Using Akavache and HttpClient with REST services

Introduction

In this article, we are going to cover how to use HttpClient to make an HTTP Request and cache the response offline using Akavache.

Setting Up

Let's get started by installing the System.Net.Http NuGet package into our shared PCL project i.e. FormsTutor. This package will give us what we need to make our HTTP Request.

Next, we will install the Akavache NuGet package into all three of our projects i.e. FormsTutor, FormsTutor.iOS, and FormsTutor.Droid. It is important that you install NuGet into all three projects, otherwise, you will experience errors at runtime. Akavache is a key/value store, that can be used for storing data offline.

Configuration

Before we get started, we are going to setup a static class that we will use for storing configuration information in. This is a common pattern used for configuring Xamarin Apps.

Below is our new Configuration class that will sit in the FormsTutor project.

public static class Configuration
{
    public const string ApplicationName = "FormsTutor";
    public const string ApiBaseUrl = "https://raw.githubusercontent.com/jamilgeor/FormsTutor/master/Lesson06/Data/";
}

Making a Simple Get Request with HttpClient

We are now going to modify our ArticleService class that we created in one of our previous articles, to use the HttpClient class provided by the System.Net.Http NuGet package. We will use Reactive Extension methods to generate an observable that can be used inside of our ViewModel.

public interface IArticleService
{
    IObservable<IEnumerable<Article>> Get();
}

public IObservable<IEnumerable<Article>> Get()
{
    var url = $"{Configuration.ApiBaseUrl}Articles.json";
    return 
        Observable.FromAsync(() => new HttpClient().GetAsync(url))
                  .SelectMany(
                      async x =>
                      {
                          x.EnsureSuccessStatusCode();
                          return await x.Content.ReadAsStringAsync();
                      })
                  .Select(content => 
                          JsonConvert
                          .DeserializeObject<Article[]>(content));
}

The above code simply does the following:

  1. Fetch data
  2. Read data
  3. Convert data

This may seem a little overkill for what we are trying to achieve with this code, however we will expand on this in a later article to show how we can further use Reactive Extensions to provide some additonal functionality that would be alot harder to achieve otherwise.

Sample reference:
https://stackoverflow.com/questions/20462378/can-i-simplify-using-observable-create-to-return-objects-from-a-json-request

Setting up Akavache

Before we start using Akavache to cache our data offline, there are a couple of additional tasks we must perform first. Below you will see that we first set the application name of the Akavache BlobCache. We then flush each of the BlobCache's when the application goes to sleep. This is to ensure that any operations on our cache are completed before the application is put to sleep.

public App()
{
    InitializeComponent();

    BlobCache.ApplicationName = Configuration.ApplicationName;

    MainPage = new ArticlesPage();
}

protected override void OnSleep()
{
    var caches = new [] 
    { 
        BlobCache.LocalMachine, 
        BlobCache.Secure, 
        BlobCache.UserAccount, 
        BlobCache.InMemory 
    };

    caches.Select(x => x.Flush())
          .Merge()
          .Select(_ => Unit.Default)
          .Wait();
}

At the time of writing this article, there is currently a discussion taking place as to what the best way to ensure that all caches are safely flushed and disposed of correctly on application shutdown. You can read more about this here:
https://github.com/akavache/Akavache/issues/342

In this example, I am calling the Flush method on each of the cache's when application OnSleep method is called. The first part of the method initializes an array of all the different BlobCache's instantiated by Akavache and the second part simply calls the Flush method on each of them. Because the Flush method returns an Observable, we merge all Observable sequences and then wait for them to complete.

Working with Akavache

Fetching Cached Data with Akavache

Next, we will define a method to fetch our data from the cache. If the data does not exist in the cache, we will need to fetch it using our Article Service member. Akavache provides a super convienient method for doing this, GetOrFetchObject allows you to specify a fallback method to fetch data from an external service if the data doesn't exist in cache.

IObservable<IEnumerable<Article>> LoadArticlesFromCache()
{
    return BlobCache
        .LocalMachine
        .GetOrFetchObject<IEnumerable<Article>>
        (CacheKey, 
         async () => 
            await _articleService.Get(), CacheExpiry);
}

Caching Data with Akavache

Next, we will define a method for inserting our data into the cache. For the ArticlesViewModel we are going to use LocalMachine as the location to store our cached articles list. To read more about how each of the different cache locations works and when to use them, go here https://github.com/akavache/Akavache#choose-a-location.

void CacheArticlesImpl(IEnumerable<Article> articles)
{
    BlobCache
        .LocalMachine
        .InsertObject(CacheKey, articles, CacheExpiry)
        .Wait();
}

Next, we will change our LoadArticleImpl method to either load a list of articles from the cache, or retrieve them directly from the Article Service. Basically what we are trying to do here is, if there are no articles loaded already, attempt to fetch them from the cache, to provide users with instant feedback. Otherwise, if we have articles loaded already, then we want to fetch the latest articles directly from the server, and bypass the cache. This is because a user would expect when they force a refresh of articles, that they are seeing the latest articles avaible.

IObservable<IEnumerable<Article>> LoadArticlesImpl()
{
    return !Articles.Any() ? 
        LoadArticlesFromCacheImpl() : 
        _articleService.Get();
}

Next, we will wire everything up in our View Model's constructor. The code inside of the constructor does the following:

  1. Create command LoadArticles which calls the LoadArticlesImpl method
  2. After the first time the LoadArticles command is run, cache the results of the command
  3. Map the results of the command every time using the MapArticles method

The reason that we want to skip the caching of articles on the first execution of the command, is that we are fetching the articles from the cache on first load, so there isn't any need to put them back into the cache.

public ArticlesViewModel()
{
    _articleService = new ArticleService();
    Articles = new ReactiveList<Article>();
    LoadArticles = ReactiveCommand
                   .CreateFromObservable
                    (LoadArticlesImpl);

    LoadArticles.Skip(1)
                .Subscribe(CacheArticlesImpl);

    LoadArticles.ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(MapArticlesImpl);
}

Summary

Akavache is a really useful tool to have when you are developing apps, having the ability to dump and retrieve data with very little code is invaluable. We'll be using Akavache extensively through this series of articles, so I suggest heading over to https://github.com/akavache/Akavache to get some more detailed information on using it.

Full source code for this article can be found here:
https://github.com/jamilgeor/FormsTutor/tree/master/Lesson06

Note

You'll notice in the example source code, the ListView has been slightly updated to render the list correctly.

References

https://github.com/akavache/Akavache

https://github.com/akavache/Akavache/issues/342

https://stackoverflow.com/questions/20462378/can-i-simplify-using-observable-create-to-return-objects-from-a-json-request

https://github.com/akavache/Akavache#choose-a-location