Just Use Akavache, Again.

On Tuesday we talked about getting started with Akavache, a tool to write data to disk that works on every major platform.

Akavache 4.x has some very cool features that might not be easily discoverable. And that's what we're going to talk about: the cool stuff.

Getting and Fetching Data

  • We start with a very clunky part of the demo from the first post. In the demo, the MainActivity checks the cache. If there is nothing in the cache, the MainActivity calls the service. When the service responds, the MainActivity stores the data in the cache so that if the code is searched again, the cache will deliver. Sound tedious? It is.

To refresh our memories, here is the old sample code.

//check the cache
   var airport = await _cache.GetAirport(airportCode.Text);

   //if the cache doesn't have it, call the service
if (airport == null)
{
    airport = await _service.GetAirportByCode(airportCode.Text);
}
AndHUD.Shared.Dismiss();

   //set and cache the data
if(airport != null)
{
    code.Text = airport.code;
    name.Text = airport.name;
       location.Text = airport.location;
    comment.Text = airport.comments;
    //insert that data
    _cache.StoreAirport(airport);
 }

I wouldn't put this in an activity as is. This design was just for demonstration purposes.

And here is the get method from the data cache:

public async Task<Airport> GetAirport(string airportCode)
    {
        try
        {
            return await BlobCache.LocalMachine.GetObject<Airport>(airportCode.ToLower());
        }
        catch(KeyNotFoundException ex)
        {
            //for now we want to have the service go get information
            return null;
        }
    }

I could've had the data cache go get the data on the KeyNotFoundException, but that is a lot of extra code.

As you can see in the code above, this method is complicated and requires a lot of boilerplate code. Fortunately, there is an easier way, using Akavache's GetOrFetchObject feature.

public async Task<Airport> GetAirport(string airportCode)
    {
        return await BlobCache.LocalMachine.GetOrFetchObject<Airport>(airportCode.ToLower(),
            async () => await _service.GetAirportByCode(airportCode));
    }

Please allow me to explain some of the awesomeness. First we check the cache for the key. If there is not an item in the cache, then we execute the FetchTask we provided. Akavache then caches the data returned in the FetchTask. To top it off, this all happens asyncronously. This is my favorite feature to ever be created.

In the the demo, the MainActivity's code is reduced to this:

//get the data
var airport = await _cache.GetAirport(airportCode.Text);

AndHUD.Shared.Dismiss();

//set the data
   if(airport != null)
{
    code.Text = airport.code;
       name.Text = airport.name;
       location.Text = airport.location;
    comment.Text = airport.comments;
}

No more hoops to jump through to determine what to do next. In a true application the dependency to the Webservice class would be dropped, which is always good.

Expiring your data

  • If you want your cached data to expire after a certain period of time, you can.

    public async Task<Airport> GetAirport(string airportCode)
    {
        return await BlobCache.LocalMachine.GetOrFetchObject<Airport>(airportCode.ToLower(),
            async () => await _service.GetAirportByCode(airportCode),
            DateTimeOffset.Now.AddMinutes(1));
    }
    

    After a minute, this data will expire and then a webservice call will happen the next time it is requested. It doesn't make sense for airport codes, but I find it very useful in my apps to keep data from getting too stale or if I simply don't care to keep it after a set amount of time.

  • To clean expired keys, there is a Vacuum function.

    public async Task CleanTheCache()
    {
         await BlobCache.LocalMachine.Vacuum();
    }
    

Working with images in a way that doesn't make you want to break your computer

  • You can also load and persist images. This leverages another library called Splat, which makes cross platform images a breeze.

    public async Task<Splat.IBitmap> GetAnImage()
    {
       return await BlobCache.LocalMachine.LoadImageFromUrl("http://images.trsneed.com/blogstuff/Akavache/Aerial_view_of_San_Francisco_International_Airport_2010.jpg", false, 200,200);
    }
    

    The false statement in the code above tells Akavache to persist the response image. A cool part about this is that akavache caches it as a byte array, and you already know the key (it's the image's url). If you want to fetch the image everytime (because you hate saving bandwidth), set that bool to false.

    in conclusion

    In this post we have looked into several of the neat features of Akavache:

    1. GetOrFetchObject, using this dramatically reduces boilerplate code that comes with many caching attempts/frameworks.
    2. Expiring data, if you want to keep data around for only a set period of time, you can expire the key.
    3. LoadImageFromUrl, this makes cross platform image handling much easier and is a huge bandwidth saver due to the automatic caching.

    There is a repository here with all my sample code, the app looks a bit like this:

image of app running

As always code samples are on GitHub, the source code to Akavache is here, the code to this sample is here.

comments powered by Disqus