Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Visual Studio Achievements for Windows Phone - Storage

12.13.2011
| 2979 views |
  • submit to reddit

Previous parts, in case you missed them:

As Niners are added to the application, it makes sense to store them, so that the application user won't have to create the same list over and over again. To do this, we could go two ways: either use the SQL Compact system or rely on plain files in the Isolated Storage. To make a better decision, let's look at the data that I have available and might want to preserve.

I am working with a single collection that contains multiple Niner instances. Each Niner instance has various user metadata associated with it:

It makes sense to save the user alias since it is static and is not changed. All other properties are essentially dynamic and can be loaded on application startup. Since this is the only informational set that is stored, SQL CE looks like an overkill, so an XML file in the Isolated Storage will do the job just fine. 

The next question is whether I should serialize the collection or simply iterate through the names creating individual nodes? Serialization will require much less code and makes everything a bit more efficient - after all, someday we might want to store additional values and iterating through the whole list might cause some compatibility problems.

Let's start by modifying the Niner class to include the XmlIgnore attribute for public properties that are not used. Here is what it looks like:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;

namespace VisualStudioAchievements
{
    public class Niner : INotifyPropertyChanged
    {
        string _alias;
        public string Alias
        {
            get
            {
                return _alias;
            }
            set
            {
                if (_alias != value)
                {
                    _alias = value;
                    NotifyPropertyChanged("Alias");
                }
            }
        }

        string _name;
        [XmlIgnore]
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    NotifyPropertyChanged("Name");
                }
            }
        }

        Uri _avatar;
        [XmlIgnore]
        public Uri Avatar
        {
            get
            {
                return _avatar;
            }
            set
            {
                if (_avatar != value)
                {
                    _avatar = value;
                    NotifyPropertyChanged("Avatar");
                }
            }
        }

        List<Achievement> _achievements;
        [XmlIgnore]
        public List<Achievement> Achievements
        {
            get
            {
                return _achievements;
            }
            set
            {
                if (_achievements != value)
                {
                    _achievements = value;
                    NotifyPropertyChanged("Achievements");
                }
            }
        }

        string _caption;
        [XmlIgnore]
        public string Caption
        {
            get
            {
                return _caption;
            }
            set
            {
                if (_caption != value)
                {
                    _caption = value;
                    NotifyPropertyChanged("Caption");
                }
            }
        }

        int _points;
        [XmlIgnore]
        public int Points
        {
            get
            {
                return _points;
            }
            set
            {
                if (_points != value)
                {
                    _points = value;
                    NotifyPropertyChanged("Points");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

The serialization of collection contents can occur at two events - when the collection is modified or when the application is closed. Performance-wise, it wouldn't make much sense to update the file every time a new Niner is added. When the application is closed, however, it is the perfect time to store the alias values for future reuse.

To avoid confusion, you should remember that the application itself has two "hiding" event handlers:

  • Application_Closing
  • Application_Deactivated

When the application is closes, the OS removes any trace of the application itself in the RAM and all data that was not stored is lost. On deactivation, the application is tombstoned - the process still gets terminated but there is a record of the application stack, so that the system can resume the application's work once the process that took the foreground is finished.

For more information on application states, I recommend reading Understanding the Windows Phone Application Execution Model, Tombstoning, Launcher and Choosers, and Few More Things That Are on the Way – Part 1

We need to serialize data in both cases. To do this, I created a single method called SerializeNiners in a static class named Util - it will be used for various helper methods along the way.

public static void SerializeNiners()
{
    var userStore = IsolatedStorageFile.GetUserStoreForApplication();

    using (var stream = new IsolatedStorageFileStream("niners.xml", FileMode.Create, userStore))
    {
        XmlSerializer serializer = new XmlSerializer(BindingPoint.Niners.GetType());
        serializer.Serialize(stream, BindingPoint.Niners);
    }
}

When it comes to deserialization, the reverse method applies:

public static void DeserializeNiners()
{
    var userStore = IsolatedStorageFile.GetUserStoreForApplication();

    using (var stream = new IsolatedStorageFileStream("niners.xml", FileMode.Open, userStore))
    {
        XmlSerializer serializer = new XmlSerializer(BindingPoint.Niners.GetType());
        var niners = (ObservableCollection<Niner>)serializer.Deserialize(stream);

        NinerReader reader = new NinerReader();

        foreach (Niner niner in niners)
        {
            reader.AddNiner(niner.Alias, true);    
        }
    }
}

Notice that since I am only storing the aliases, I need to re-download dynamic data. I don't instantly initialize the Niners collection and instead let the NinerReader instance do it for me.

You should add a reference to System.Xml.Serialization DLL in order to be able to use the XmlSerializer class. 

In the App class, make sure you add two calls. One in Application_Closing:

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
    Util.SerializeNiners();
}

And another one in Application_Launching:

// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
    Util.DeserializeNiners();
}

Now you have the Niners stored for continuous reuse. As a quick reminder, you can pull the latest source here.