Saving Application State in Windows Phone 7

In the last post, I created a small app to store the page state. In this blog post,  I will try to create an app that stores some counters across the application lifetime. We will store two counters: 1. How many times this app was used 2. Last datetime this app was used.
We will create a simple app using the MVVM pattern. First up, lets take a look at the ViewModel called CounterViewModel.
public class CounterViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private int m_AppCounter;
        public int AppCounter
        {
            get
            {
                return m_AppCounter;
            }
            set
            {
                if (m_AppCounter != value)
                {
                    m_AppCounter = value;
                    OnPropertyChanged("AppCounter");
                }
            }

        }

        private DateTime m_LastVisit;
        public DateTime LastVisit
        {
            get
            {
                return m_LastVisit;
            }
            set
            {
                if (m_LastVisit != value)
                {
                    m_LastVisit = value;
                    OnPropertyChanged("LastVisit");
                }
            }
        }

        public DateTime SessionStartTime { get; set; }
    }
The ViewModel implements the INotifyPropertyChanged interface to update UI whenever properties change. AppCounter is to store the number of times this app is used. LastVisit is to display the time this app was last used. SessionStartTime is a variable to store current time when this app was launched/activated. When we close/deactivate the app, we change the LastVisit to store SessionStartTime. Now, lets take a look at the App.Xaml where we supply values to these variables:
public static CounterViewModel CounterViewModel { get; set; }

private void Application_Launching(object sender, LaunchingEventArgs e)
{
       CounterViewModel = CounterRepository.GetCounters();
       CounterViewModel.AppCounter++;
       CounterViewModel.SessionStartTime = DateTime.Now;
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    CounterViewModel.AppCounter++;
    CounterViewModel.SessionStartTime = DateTime.Now;
    CounterViewModel = CounterRepository.GetCounters();
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    CounterViewModel.LastVisit = CounterViewModel.SessionStartTime;
    CounterRepository.SaveCounters(CounterViewModel);
}

private void Application_Closing(object sender, ClosingEventArgs e)
{
    CounterViewModel.LastVisit = CounterViewModel.SessionStartTime;
    CounterRepository.SaveCounters(CounterViewModel);
}
First, we add a static variable for the ViewModel in the app.xaml class. This static object will store our counters. In the Launching/Activated method, we get the counters from our store (we will see that in a bit). We increase the app counter and store the current time in SessionStartTime. In the Closing/Deactivated, we assign the LastVisit variable to store SessionStartTime and then we save the counters. We should be careful about what code we put in Launching/Activated methods, as there is a time limit of 10 secs. If within 10 seconds these methods don't finish execution, an exception is thrown.
The CounterRepository class code is shown below:
public class CounterRepository
    {
        internal static CounterViewModel GetCounters()
        {
            if (PhoneApplicationService.Current != null
                && PhoneApplicationService.Current.State.ContainsKey("counterViewModel")
                && PhoneApplicationService.Current.State["counterViewModel"] is CounterViewModel)
            {
                return PhoneApplicationService.Current.State["counterViewModel"] as CounterViewModel;
            }
            else
            {
                IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();

                if (store.DirectoryExists("data") && store.FileExists("data\\counters.xml"))
                {
                    using (var file = store.OpenFile("data\\counters.xml", FileMode.Open))
                    {
                        var serializer = new XmlSerializer(typeof(CounterViewModel));
                        var result = serializer.Deserialize(file);
                        return result as CounterViewModel;
                    }
                }
                else
                {
                    return new CounterViewModel()
                    {
                        AppCounter = 0
                    };
                }
            }
        }

        internal static void SaveCounters(CounterViewModel cvm)
        {
            SaveToPhoneApplicationService(cvm);
            SaveToIsolatedStorage(cvm);
        }

        private static void SaveToPhoneApplicationService(CounterViewModel cvm)
        {
            if (PhoneApplicationService.Current != null)
                PhoneApplicationService.Current.State["counterViewModel"] = cvm;
        }

        private static void SaveToIsolatedStorage(CounterViewModel cvm)
        {
            IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();

            if (!store.DirectoryExists("data"))
                store.CreateDirectory("data");

            if (store.FileExists("data\\counters.xml"))
                store.DeleteFile("data\\counters.xml");

            using (var file = store.CreateFile("data\\counters.xml"))
            {
                var serializer = new XmlSerializer(typeof(CounterViewModel));
                serializer.Serialize(file, cvm);

                file.Flush();
                file.Close();
            }
        }
    }
In the SaveCounters method, we take care of both tombstoning and closing the application cases. In case, user moves away from your app by opening a browser, application is not closed but deactivated. This is called tombstoning. In case, the user presses a back button, the application is closed. In case of tombstoning, we have the option of using PhoneApplicationService. This class provides access to various aspects of Application lifetime. In case the application is closed, PhoneApplicationService will not exist. So we use the concept of IsolatedStorage to store the variables outside of the application in phone's memory store. We create a folder to store the data. Then we take the ViewModel and serialize to get an xml file, which is then stored into the folder.
In the GetCounters method, we do the reverse. We first check if the counters are available in the PhoneApplicationService. If they are not, we fetch the counters from IsolatedStorage.
Lastly, lets take a look at the XAML file, which is pretty simple.
<Grid x:Name="ContentPanel" Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0" FontSize="24" Text="App Counter: " />
            <TextBlock Grid.Row="0" Grid.Column="1" FontSize="24" Text="{Binding Path=AppCounter}" />
            <TextBlock Grid.Row="1" Grid.Column="0" FontSize="24" Text="Last Visit: "/>
            <TextBlock Grid.Row="1" Grid.Column="1" FontSize="24" Text="{Binding Path=LastVisit}" />
        </Grid>
In the cs file, we just set the DataContext property to the ViewModel. And then in the XAML, we bind the TextBlocks to display these values.
When we start the app, you see the count as 1 and datetime as default datetime. This can be easily changed to probably show no last visit date. This scenario is displayed in image 1. Then, click the hardware search button to depict the Deactivated case. Image 2 shows the search screen that comes up. Then click the hardware back button to go back to the app and activate it. As you can see in image 3, the counter has increased and last visit datetime has changed. In this case, these values were stored in the PhoneApplicationService. Then, click on the hardware back button again to depict the closing case. The application is closed and you see image 4. Once you click the AppCounter app, you again see the results as in image 5.
1 2 3 4 5

1 comment:

Alex Bandit said...

I’m wondering how I might be notified whenever a new post has been made. I’ve subscribed to your RSS feed which must do the trick! Have a great day! it help