C# Corner
Building a Windows 8 Metro App, Part 3: Putting it Together
Eric Vogel covers how to use the Windows 8 local data storage APIs to cache application data.
Welcome to the third and final installment in the building a Windows 8 Metro App series. In Part 1, I covered how to create a basic RSS reader application. In Part 2, I went over how to implement a two-way share contract. Today I'll cover how to use local storage to persist data across application sessions and suspensions. I'll be updating the RSS reader application from the previous two articles to be able to cache an RSS feed locally. When the user loads up the application, the cached version will be retrieved and loaded if it exists. When the user loads up a new feed the application will cache it.
Windows 8 Local Storage Basics
Before I dive too deep into updating the RSS Reader application, let's go over the basics of the Windows 8 local storage story. WinRT includes several local storage methods, which are available in the Windows.Storage namespace. The most simple type of storage is the LocalSettings data store, which is well suited for storing application configuration settings and other very simple data. For example, you could store a user preference named "EnablePush" for enabling push notifications as a bool value.
Windows.Storage.ApplicationData.Current.LocalSettings.Values["EnablePush"] = true;
The Values property is a Dictionary<string,Object> so you can store more complex objects as well. But I've run into issues trying to store, for example, a Uri in the LocalSettings.Values dictionary and would advise using it only for simple objects and value types.
To remove a local setting, you can call LocalSettings.Values.Remove and pass the key of the setting to be removed. You can also store a group of settings, referred to as a container. For example, you could create a "General" settings container with "Version" and "Registered" settings.
LocalSettings.CreateContainer("General", ApplicationDataCreateDisposition.Always);
LocalSettings.Containers["General"].Values["Version"] = 1.0;
LocalSettings.Containers["General"].Values["Registered"] = true;
The Windows.Storage namespace also includes asynchronous file input and output operations as I'll cover later when updating the RSS reader. On to the main show.
Adding Local Storage to RSS Reader Application
To get started, open up the RSS Daily Reader application solution (found in the code download from Part 2 of this article). Add a new project to the solution and name it "VSMWinRTDemo.Storage". Now add a new class file named SettingsStorage.
SettingsStorage will be a small utility class for quickly accessing the application's local settings. It contains two methods: GetLocalSetting and SetLocalSetting, that get and set a local setting for the application. The GetLocalSetting method allows you to retrieve a type-safe version of a local setting. The SetLocalSetting method simply stores a local setting value indexed by the given key. See Listing 1 for the full code.
Now to add the LocalStorage class, which will allow storage of more complex objects across the application life-cycle. The LocalStorage class asynchronously stores a string indexed dictionary of objects to a local file. In the case of the RSS reader application, it will be used to store a cached copy of an RSSFeed object, which contains a collection of RSS feed items. First you need to add the necessary using statements for the Windows Storage, IO, Serialization and Streams access.
using Windows.Storage;
using System.IO;
using System.Runtime.Serialization;
using Windows.Storage.Streams;
Now to add the backing string-Object dictionary to store data in memory before dumping it to file storage.
static private Dictionary<string, object> _data = new Dictionary<string, object>();
private const string filename = "localSession.xml";
static public Dictionary<string, object> Data
{
get { return _data; }
}
The GetItem<T> method allows for retrieval for a type-converted value from the local storage dictionary, similar to the GetLocalSetting method in the SettingsStorage class.
static public T GetItem<T>(string key)
{
T result = default(T);
if (_data.ContainsKey(key))
{
result = (T)_data[key];
}
return result;
}
The ContainsItem method simply checks for the presence of a given item in the data dictionary.
static public bool ContainsItem(string key)
{
return _data.ContainsKey(key);
}
The SaveAsync method, shown in Listing 2, asynchronously creates a file and writes a serialized version of the data dictionary to the file.
RestoreAsync (Listing 3) is the opposite of SaveAsync -- it restores the data dictionary from the locally-saved file asynchronously.
The Save method calls the SaveAsync method on a background thread.
static async public Task Save()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) =>
{
LocalStorage.SaveAsync().Wait();
}, Windows.System.Threading.WorkItemPriority.Normal);
}
The Save and Restore wrapper methods are needed for the developer preview release of WinRT to get around some threading issues. Kudos to Microsoft for their excellent sample code that demonstrates the work-around fix. See Listing 4 for the full LocalStorage class code.
It's almost time to use the LocalStorage class. First, it's important to understand the Windows 8 application lifecycle. There are two application events you'll use for most applications: OnLaunched and OnSuspending. The OnLaunch event is fired when the application first loads, and when the application is resumed from a suspension. An application is usually suspended after it's been idle for a prolonged duration.
In the OnSuspending event, it's good practice to save any of your needed application state and notify the OS when you're finished. In the OnLaunched event you can check the previous application state and restore any needed state. Luckily you have the LocalStorage class ready to use, and can subscribe to the events and be done with it. Open up Application code-behind file (App.xaml.cs). Now subscribe to the Suspending event of the application in the constructor.
public App()
{
InitializeComponent();
this.Suspending += new SuspendingEventHandler(App_Suspending);
}
Next, locate the OnLaunched event and add the following code to restore the LocalStorage session if the application had been terminated previously.
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
await LocalStorage.Restore();
}
Now, in the App_Suspending event, save the current application state to local storage and notify the OS.
async protected void OnSuspending(object sender, SuspendingEventArgs args)
{
SuspendingDeferral deferral = args.SuspendingOperation.GetDeferral();
await LocalStorage.Save();
deferral.Complete();
}
See Listing 5 for the completed code.
Updating the RSS Application
Now to update the UI (see Figure 1) for the application to allow the user to set a default RSS feed, as well as save the number of RSS items to display. Open up MainPage.xaml and update the LayoutHeader grid with the markup shown in Listing 6. The new additions include a "Set as default button", a "# Items" label and associated text box, as well as a label that displays "Cached" when a cached RSS feed is loaded.
[Click on image for larger view.] |
Figure 1. The updated Daily Reader UI |
You're in the home stretch. Add a reference to the VSMWinRTDemo.Storage project to the UI project. Next, open up MainPage.xaml.cs and get ready. First add a using statement for the new Storage project.
using VSMWinRTDemo.Storage;
Then, add the local setting key constants for the default feed URL and the maximum number of feeds.
const string DEFAULT_FEED_KEY = "DefaultFeedUri";
const string MAX_FEED_ITEMS_KEY = "MaxFeedItems";
Next, subscribe to the LostFocus event for the NumFeeds field in the MainPage class constructor.
public MainPage()
{
..........................
NumFeeds.LostFocus += new RoutedEventHandler(NumFeeds_LostFocus);
}
The LostFocus event gets the current user-entered value for the NumFeeds field and stores it into the LocalSettings object through the SettingsStorage class.
void NumFeeds_LostFocus(object sender, RoutedEventArgs e)
{
if (!String.IsNullOrWhiteSpace(NumFeeds.Text))
{
_maxFeeds = int.Parse(NumFeeds.Text);
SettingsStorage.SetLocalSetting(MAX_FEED_ITEMS_KEY, _maxFeeds);
}
}
Next, update the MainPage_Loaded (Listing 7) event to load the maximum number of feeds and the default URL from the SettingsStorage class. If there's a default URL, I set the FeedUrl field as well as load up the RSS feed in the application via GetFeeds.
In the DefaultButton click, set the LocalSetting for the currently entered feed URL from the FeedUrl field.
private void SetDefaultButton_Click(object sender, RoutedEventArgs e)
{
SettingsStorage.SetLocalSetting(DEFAULT_FEED_KEY, FeedUrl.Text);
}
Next, add a new method named BindFeeds that sets the DataContext for the Page to the given RSSFeed. The method also caches the feed to local file storage through the LocalStorage Data dictionary indexed via the Feed URL.
private void BindFeeds(RSSFeed feeds, string url)
{
this.DataContext = feeds;
LocalStorage.Data[url] = feeds;
}
Finally, I update the GetFeeds method to see if there's a cached version of the feed first. If there is a cached copy, it's loaded; if not, a live version of the feed is retrieved. If a cached version is displayed, the Cached label is set to be displayed. If a live copy is displayed, the Cached label is hidden.
private async Task GetFeeds(string url)
{
RSSFeed feeds = null;
if (LocalStorage.ContainsItem(url))
{
feeds = LocalStorage.GetItem(url);
Cached.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
else
{
Cached.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
feeds = await _client.GetFeeds(url, _maxFeeds);
}
BindFeeds(feeds, url);
}
See Listing 8 for the full MainPage.xaml.cs code, which should look like Figure 2.
[Click on image for larger view.] |
Figure 2. Loading a Cached RSS Feed. |
Limited Options
As you can see, there are a few local storage options available in WinRT. For storing configuration settings, using the LocalSettings store is preferred. In the current development preview of WinRT, the options for storing more complex data objects is limited to file storage options. I'm thinking that as WinRT gets closer to release that relational local data stores will become available as well. All code samples are available in the code drop.
About the Author
Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].