Advanced data caching in Windows Store apps

Advanced data caching in Windows Store apps

In this post we’ll be working with data caching in Windows Store apps using Q42.WinRT library and Flickr API, continuing from the last post “Data caching in Windows 8 apps”. Using the library, it’s very simple to do a lot more than just storing the data to local storage.

The class we’ll be using today is called DataLoader, and it can be found in Q42.WinRT.Data namespace. It has three important bool properties which are easily bindable to your UI:

  • IsError
  • IsBusy
  • IsFinished

This way the class handles the loading states for you, exposing the bool properties that you can bind. The class also implements a method called LoadAsync, with the following signature:

public Task LoadAsync(Func<Task> loadingMethod, Action resultCallback = null, Action errorCallback = null);  

It has three important parameters, the loading method, callback method and the error (exception) method. Here’s how to implement it. Add a DataLoader object to the GroupedItemsPage code behind from the last blog post.

DataLoader _dataL = new DataLoader();

public DataLoader DataL  
{
    get
    {
        return _dataL;
    }
    set
    {
        _dataL = value;
    }
}

Exposing it as public to bind it to the UI in the LoadState method.

this.DefaultViewModel["DataLoader"] = DataL;

Then call the LoadAsync method, like this:

    var x = DataL.LoadAsync(
                async () =>
                {
                    var client = new HttpClient();
                    string someUrl = "http://api.flickr.com/services/rest/?method=flickr.photos.getRecent&amp;api_key=YOUR_API_KEY&amp;format=json";
                    var tempResult = await client.GetStringAsync(new Uri(someUrl, UriKind.Absolute));
                    var resultSubstring = tempResult.Substring(14, tempResult.Length - 15);
                    var photosResult = await JsonConvert.DeserializeObjectAsync(resultSubstring);
                    return photosResult;
                }, (result) =>
                    {
                        this.DefaultViewModel["Groups"] = result.photos.photo;
                    }, async (exc) =>
                        {
                            var msg = new MessageDialog(exc.Message, "Error");
                            await msg.ShowAsync();
                        });

The first method is asynchronous – you create a HttpClient, get string asynchronously and return the result. In the result callback method you bind it to the UI. Third part is also asynchronous because we await message dialog. When you think about it, this call is nothing more than a simple try – catch block in which you try to get a string using a HttpClient, bind it to the UI and fail gracefully if something ugly happens in the process. The only obvious benefit of using this is the fact that by using the DataLoader you can easily bind the loading state to the UI, not worrying about setting the bool properties yourself. So, if you add this to the GroupedItemsPage.xaml

    <ProgressRing Grid.RowSpan="2"
                      Width="40"
                      Height="40"
                      Margin="0,32,32,0"
                      HorizontalAlignment="Right"
                      VerticalAlignment="Top"
                      Foreground="White"
                      IsActive="True"
                      Visibility="{Binding DataLoader.IsBusy,
                                           Converter={StaticResource BoolToVisConverter}}" />

Where BoolToVisConverter is a standard bool to visibility converter, you’ll get a nice progress ring displayed while the data is being fetched. The same process can be used for binding the IsError and IsFinished bools.

This is not really caching, but serves as an introduction to the next method, called LoadCacheThenRefreshAsync

public Task LoadCacheThenRefreshAsync(Func<Task> cacheLoadingMethod, Func<Task> refreshLoadingMethod, Action resultCallback = null, Action errorCallback = null);  

Other than fetching the data from the Internet and the callback methods, this method first takes the cached data (if there is any), making it possible to load the app (actually, the data) much much faster, and after that take the fresh data online if possible. This is one of the two strategies you can take when working with cached data:

  1. Load cache –> load data from the Internet –> add the new data to the UI (refresh UI)
  2. Try loading from the Internet –> fall back to cache if not possible –> fail gracefully if none is possible

Load from cache and then try refresh

In order to accomplish this, call the LoadCacheThenRefreshAsync method (for example in the LoadState method)

            this.DefaultViewModel["DataLoader"] = DataL;
            string someUrl = "http://api.flickr.com/services/rest/?method=flickr.photos.getRecent&amp;api_key=YOUR_API_KEY&amp;format=json";

            var x = DataL.LoadCacheThenRefreshAsync(
                async () => // TRY GETTING FROM CACHE
                    {
                        var result = await JsonCache.GetFromCache(CalculateMD5Hash(someUrl));
                        return result;
                    },
                    async () => // GET THE NEW DATA ONLINE
                        {
                            HttpClient client = new HttpClient();
                            var tempResult = await client.GetStringAsync(new Uri(someUrl, UriKind.Absolute));
                            var resultSubstring = tempResult.Substring(14, tempResult.Length - 15);
                            var photosResult = await JsonConvert.DeserializeObjectAsync(resultSubstring);
                            if (photosResult != null)
                                await JsonCache.Set(CalculateMD5Hash(someUrl), photosResult);
                            return photosResult;
                        }, (result) => // CALLBACK DO SOMETHING WITH THE RESULT
                            {
                                //check the difference here, refresh the UI
                                if (result != null &amp;&amp; result.photos!=null)
                                this.DefaultViewModel["Groups"] = result.photos.photo;
                            }, async (exc) => // IF EXCEPTION
                                {
                                    var msg = new MessageDialog(exc.Message, "Error");
                                    await msg.ShowAsync();
                                });

The comments in the code explain what each method does. The ways in which you compare results and refresh the UI depend on the use case, and by handling the exception callback, you can fail gracefully if something happens. This is the strategy I prefer to use in my Windows Phone apps because phones are more often used over slower networks and getting the data as soon as possible is crucial for the user experience

Load from the Internet, fallback to cache

In order to do this, I extended the Q42.WinRT library (available on GitHub under MIT license), actually the DataLoader class with a new method called LoadRefreshFallbacktoCacheAsync (obsolete - the method has now become a part of Q42.WinRT framework - name of the method is LoadFallbackToCacheAsync)

        public async Task LoadRefreshFallbackToCacheAsync(Func<Task> refreshLoadingMethod, Func<Task> cacheLoadingMethod, Action resultCallback = null, Action errorCallback = null)
        {
            LoadingState = Data.LoadingState.Loading;
            T refreshResult = default(T);
            T cacheResult = default(T);

            try
            {
                refreshResult = await refreshLoadingMethod();
                if (resultCallback != null)
                    resultCallback(refreshResult);

                LoadingState = Data.LoadingState.Finished;
            }
            catch (Exception e)
            {
                LoadingState = Data.LoadingState.Error;
                if (errorCallback != null)
                    errorCallback(e);
                else if (!_catchExceptions)
                    throw;
            }

            if (LoadingState == Data.LoadingState.Error)
            {
                try
                {
                    cacheResult = await cacheLoadingMethod();
                    if (resultCallback != null)
                        resultCallback(cacheResult);
                    LoadingState = Data.LoadingState.Finished;
                }
                catch (Exception e)
                {
                    if (errorCallback != null)
                        errorCallback(e);
                    else if (!_catchExceptions)
                        throw;
                }
            }
        }

It tries to download the fresh data over the Internet, and falls back to cache only if fetching the new data failed. This means that the response callback method gets called only once, but have in mind that the exception callback method could get called twice if both Internet data and cache data do not exist (exception is thrown). The usage is almost the same as with the LoadCacheThenRefreshAsync, only the refreshLoadingMethod is the first parameter, to make it clear that this is the method that gets called first. This is an example of how to call it (again from the LoadState method)

            var y = DataL.LoadFallbackToCacheAsync(
                async () => // GET THE NEW DATA ONLINE
                        {
                            HttpClient client = new HttpClient();
                            var tempResult = await client.GetStringAsync(new Uri(someUrl, UriKind.Absolute));
                            var resultSubstring = tempResult.Substring(14, tempResult.Length - 15);
                            var photosResult = await JsonConvert.DeserializeObjectAsync(resultSubstring);
                            if (photosResult != null)
                                await JsonCache.Set(CalculateMD5Hash(someUrl), photosResult);
                            throw new NullReferenceException();
                            return photosResult;
                        },
                        async () => // TRY GETTING FROM CACHE ONLY IF REFRESH FAILED
                            {
                                var result = await JsonCache.GetFromCache(CalculateMD5Hash(someUrl));
                                return result;
                            },(result) => // CALLBACK, DO SOMETHING WITH THE RESULT
                                {
                                    //check the difference here, refresh the UI
                                    if (result != null &amp;&amp; result.photos!=null)
                                    this.DefaultViewModel["Groups"] = result.photos.photo;
                                }, async (exc) => // IF EXCEPTION, COULD BE THROWN TWICE
                                    {
                                        var msg = new MessageDialog(exc.Message, "Error");
                                        await msg.ShowAsync();
                                    });

It’s very intuitive and similar to the first methond, and the binding is included. I use this strategy when working with Windows 8 apps beacuse they are more ofter used over Wi-Fi.

Caching is an important thing in every Windows Phone and Window 8 app. Q42.WinRT makes caching really simple in Windows Store apps, and you should use it, or extend it to server you better if you feel that something is missing. Don't forget to share this post if you found it useful! :)

Igor Ralic

igor ralic

View Comments
Microsoft Certified Solutions Developer: Windows Store Apps in C#