?

Log in

No account? Create an account
Storing data on Windows Phone - Greg [entries|archive|friends|userinfo]
Greg

[ website | gregstoll.com ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Links
[Links:| * Homepage * Mobile apps (Windows Phone, Win8, Android, webOS) * Pictures * LJBackup * Same-sex marriage map * iTunesAnalysis * Where's lunch? ]

Storing data on Windows Phone [Mar. 28th, 2013|10:06 pm]
Greg
[Tags|, ]
[Current Mood |excitedexcited]

This post is adapted from a presentation I gave at the University of Texas IEEE Computer Society on March 6, 2013

Storing data on a device is one of those things that is different on every platform, but it's crucial to most apps. (especially if you want to make them work offline) Windows Phone has a lot of good options for storing data - let's look at four of them:
- Resource packaged with the app - an ideal way to ship data with your app
- Isolated Settings - best for lightweight and small data, such as app settings
- Isolated Storage File - best for storing full files
Serializing to/from JSON - a brief aside; useful when packaging a resource with the app or storing data in an Isolated Storage File
- Local database - best for fully structured data that needs to be high performance

Resource packaged with the app
This is a great way to ship static data with an app. For example, in my Marriage Map app, I ship a static version of the marriage data so that if the phone is offline during first launch of the app, we can still show some data. (if the phone is online, it downloads the current data and saves it to Isolated Storage)

Anyway, this is pretty straightforward. Add the file to your project, select the file, and in the Properties window set the Build Action to Content. After this, you can read the file with:

var resource = System.Windows.Application.GetResourceStream(
    new Uri(@"Data\stateData.js", UriKind.Relative));
using (StreamReader sr = new StreamReader(resource.Stream))
{
    string allDataString = sr.ReadToEnd();
}
If you're going to be storing data in this file, I'd recommend using JSON format - see Parsing JSON for details.

Isolated Settings
Isolated Settings are great for storing very small bits of data, like user preferences. If you want to make a settings page and have that data automatically stored in Isolated Settings, see my previous posts on adding settings to your app and adding enum settings to your app.

The class we'll be using is System.IO.IsolatedStorage.IsolatedStorageSettings - it's implements a simple Dictionary<TKey,TValue> interface to read and write. To write data, use

IsolatedStorageSettings.ApplicationSettings["NumQuizzes"] = 3;
and to read it, use

int nQ = (int)IsolatedStorageSettings.ApplicationSettings["NumQuizzes"];
Isolated Settings are backed by an Isolated Storage File, which we'll talk about next!

Isolated Storage File
For more complex data, you can move up to using the full Isolated Storage API to store data in files. There's a full filesystem you have total control over. (which is only accessible to your app, of course!) I use this in FlightPredictor to store the user's flights.

The main class we'll use is System.IO.IsolatedStorage.IsolatedStorageFile. To write to a file, use

using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
    using (var stream = store.OpenFile("flights.json", System.IO.FileMode.Create))
    {
        using (var streamWriter = new StreamWriter(stream))
        {
            streamWriter.Write("{\"flights\": []}");
        }
    }
}
and to read from it, use

using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
    using (var stream = store.OpenFile("flights.json", FileMode.Open))
    {
        using (var streamReader = new StreamReader(stream));
        {
            string flightsString = streamReader.ReadToEnd();
        }
    }
}

There are other methods on IsolatedStorageFile like CreateDirectory() and GetFileNames() if you want to really use isolated storage as a filesystem.

Serializing to/from JSON
See this post for comparing parsing time for different file formats, including JSON
Note that these APIs let you read and write text to files. Usually, you'll want to store more structured data, and I'd recommend using the JSON format because Json.NET makes it very easy. Here's how!

First, you can use the DataContract and DataMember attributes on an existing class. For example, here are the first few lines of my Flight class:

[DataContract]
public class Flight : INotifyPropertyChanged, IComparable<Flight>
{
    [DataMember]
    public string AirlineName;
    [DataMember]
    public int Number;
    [DataMember]
    public DateTime DepartureTime;
Then, to turn a list of Flights into a string, use:

List<Flight> flights = new List<Flight>();
flights.Add(flight);
string flightsString = JsonConvert.SerializeObject(flights);
and to read a list of Flights, use:

List<Flight> newFlights =
    JsonConvert.DeserializeObject<List<Flight>>(flightsString);
If you'd rather not create a whole class, you can also deserialize "raw" JSON, which is very handy when you're getting results from a webservice. For example:

JObject o = JObject.Parse(responseString);
JObject flightsJson = (JObject)o["flights"];
int numFlights = (int)flightsJson["total_entries"];
JObject flightJson = (JObject)((JArray)flightsJson["array"])[0];

Local database
You can also store data in a full database. This takes a bit more coding, but is useful if you need to do queries, etc. I use this in PhotoNotes - each photo gets a row in the database with a caption and audio filename. (which are stored in Isolated Storage Files!) Here's the topic on MSDN about a local database, but briefly, the steps you need are:
First, declare your DataContext:

public class PicturesDataContext : DataContext
{
    // Specify the connection string as a static, used in main page and app.xaml
    public static string DBConnectionString = "Data Source=isostore:/PictureNotes.sdf";

    // Pass the connection string to the base class.
    public PicturesDataContext(string connectionString)
        : base(connectionString) { }

    // Specify a single table
    public Table PictureNotes;
}
Then on your table class, you need the Table attribute:

[Table]
public class PictureNote : INotifyPropertyChanged, INotifyPropertyChanging
and then member fields with the Column attribute are columns in the table:

[Column(CanBeNull=true)]
public string FileName
{
Now, to insert rows, you can create new instances of the PictureNote class and call InsertOnSubmit() on the table:

_noteLensDB.PictureNotes.InsertOnSubmit(new PictureNote()
    { FileName = shortFileName, NoteText = noteText,
      NoteAudioFileName = _lastSavedAudioFileName });
_noteLensDB.SubmitChanges();
And to query the table, you can use the totally cool LINQ to SQL. For example:

var query = from n in _noteLensDB.PictureNotes
            where ((n.NoteText != "" && n.NoteText != null) || n.NoteAudioFileName != null)
            select n;
var numPictures = query.Count();
foreach (var note in query)
{
    string name = note.FileName;
}


--

See all my Windows Phone development posts.

I'm planning on writing more posts about Windows Phone development - what would you like to hear about? Reply here, on twitter at @gregstoll, or by email at greg@gregstoll.com.
LinkReply