Akavache is AKA Awesome!
In the last post we covered how to implement data caching in a cross platform application using Xamarin. And we also showed why providing a data cache to the users of our mobile applications is important.
- Network resiliency
- Perceived app performance
However, the plumbing for the caching can be a bit of a pain to implement. The following diagram gives an example of the flow needed to implement caching. The order in which the tasks are implemented may vary based on the business requirements, some tasks may be omitted, but generally they are the same.
Ugh … and this is just one of many different workflows!
Then we get to the characteristics of the data we want to cache. Obviously the data’s not volatile since we’re saving it for later use. Most likely we already know where and how we want to use the data within our app, so if we thought our design through, we’re saving the data in a way that it’s easy to get it and we’re not querying it in a relational manner. And the business requirements will dictate this, but the data already in the cache may not change where we have to update it.
So what if I told you there was a framework that provided everything above (and more), and made it super easy to implement the caching plumbing in a cross platform manner?
Enter Akavache
The official definition from Akavache’s GitHub site:
Akavache is an asynchronous, persistent (i.e. writes to disk) key-value store created for writing desktop and mobile applications in C#, based on SQLite3. Akavache is great for both storing important data (i.e. user settings) as well as cached local data that expires.
In other words, Akavache provides an awesome framework for caching data. It implements everything we mentioned above, and more (and in a way most likely better than we could have ourselves). Written by Paul Betts, it’s a library that I’m finding more and more useful every time I use it.
In the rest of this post we’ll take a look at the basic API that Akavache provides. Then in the next post we’ll refactor the code from the Cold Hard Data Cache Post on caching and see how much simpler and more elegant Akavache makes it.
Basic Installation and Usage
First things first – we’re going to need to install Akavache. It’s available via NuGet. Search “Akavache” and the top 3 results will look something like the following:
I found that trying to select “Akavache”, “Akavache.Core”, and “Akavache.SQLite3” to install all at once makes Xamarin Studio unhappy. Select “Akavache.SQLite3” first and install it. Then install “Akavache” and you’ll be off and running.Akavache is asynchronous – which means it won’t block our UI. That’s not a bad thing and not even a difficult thing, just something to be aware of.
At its heart, Akavache is a key/value store with a SQLite backend. To simplify how it stores data, there’s a table which has a column for the key which is a VARCHAR and another for the value which is a BLOB. (There are also columns for the CLR type of data in the blob column, another column for the expiration date of the cached data, and another for the created date).
The cool thing about using the key/value model of persisting data is that we can put anything into the value portion. It can range from a simple object with nothing but properties, to a collection of objects, to a complex object hierarchy with nested objects within objects … etcetera and so forth.
As far as using the Akavache framework, the first thing to note is that everything in Akavache is accessed through an objected called BlobCache
.
To define where the database will reside within the app’s sandbox, we set a property on the BlobCache object called ApplicationName. So we end up with something like the following: Akavache.BlobCache.ApplicationName = “AkaAwesome”;
There’s nothing fancy going on here, this is just basic setup stuff.
The BlobCache object exposes 4 different objects which can cache data for us. Each of these object implement the IBlobCache interface.
IBlobCache | Description |
---|---|
BlobCache.LocalMachine | Local machine cache (normal SQLite as described above). |
BlobCache.InMemory | Volatile cache (gone on restart). |
BlobCache.Secure | Encrypted cache within SQLite. |
BlobCache.UserAccount | User account cache. This acts the same as the LocalMachine above, except the SQLite database file will get backed up by Apple backups, while the LocalMachine database file does not. |
When using the UserAccount cache in iOS, Akavache will ensure the SQLite database file is created in the proper location so the file is backed up (as in iCloud or iTunes backups). The database file used by the LocalMachine cache object is created in a location where it is not backed up.
The vast majority, if not all, of the time we’ll be using the LocalMachine or UserAccount object in our mobile apps. So let’s talk about getting data into, and out of the cache.
Inserting Objects
Method | Description |
---|---|
InsertObject(key, T, expiration) | Insert single object of type T with key and optional DateTimeOffset expiration |
InsertAllObjects(dictionary, expiration) | Insert all objects in IDictionary with optional expiration |
The first function above will insert any object into the cache taking an optional parameter of when it should expire – or be deleted and not be returned from the cache any longer.
The second function does the same, but with multiple objects at a time.
A quick code example of adding a Question object that expires 2 hours from now:
var question = new QuestionInfo { QuestionID = 1, Title = "Hello?" };
await BlobCache.LocalMachine.InsertObject<QuestionInfo> (
"first",
question,
DateTimeOffset.Now.AddHours (2));
Getting Objects
Method | Description |
---|---|
GetObject(key) | Return single object via its key |
GetAllObjects() | Return all objects of type T |
These methods will return an object of type T if it exists in the cache. Pretty straight forward. One thing to watch out for is that if the key does not exist, an exception of the type KeyNotFoundException
will be thrown.In action it looks like the following:
try {
var question = await BlobCache.LocalMachine.GetObject<QuestionInfo> ("first");
} catch (KeyNotFoundException) {
// do something useful
}
Fetching Objects
Method | Description |
---|---|
GetOrFetchObject(key, Func, expiration) | Returns cached result – if none exists – execute the Func parameter to load result, put it in the cache and expire it according to optional expiration parameter |
GetAndFetchLatest(key, Func>, Func, DateTimeOffset?) | Returns cached result. Also goes ahead and uses the function in the second parameter to load the latest value and put that into the cache. So this method will return twice – once with the value from the cache and again when it updates with the latest value. So as the official documentation says, awaiting this function is a "Bad Thing" and that we subscribe to it instead. |
These two functions are where Akavache really starts to become awesome! In the first function we’re saying – give me what’s in the cache – can’t find it? No worries, just call the function I’m passing in, stick the result in the cache and give me the result. That’s awesome!
var question = await BlobCache.LocalMachine.GetOrFetchObject<QuestionInfo> (
"first",
async () => await StackAPI.GetQuestionFromWeb (123),
DateTimeOffset.Now.AddHours (3)
);
The second is even better – it takes a bit to fully understand it though. We’re saying, give me what’s in the cache, but at the same time call the function I’m passing in and get the latest value. This way we can make our user interface super responsive and keep the UI and cache up to date with the latest data!
But because the function is returning twice we can’t await it – so we have to use Subscribe, so every time our value changes we’re alerted to it.
I told you – flipping awesome!
QuestionInfo augusteRodin;
BlobCache.LocalMachine.GetAndFetchLatest ("theThinker",
async () => await DownloadQuestion (), null, null).Subscribe (
cachedThenUpdatedThought => {
augusteRodin = cachedThenUpdatedThought;
Device.BeginInvokeOnMainThread (() => theStatue.Text = augusteRodin.Title);
});
Downloading
Method | Description |
---|---|
DownloadUrl(url,IDictionary(string,string) headers, fetchAlways, expiration) | Download the contents of the URL (using the URL as the key in the cache). By setting the fetchAlways boolean, can always hit the web to download. |
LoadImageFromUrl(url, fetchAlways, desiredHeight, desiredWidth, expiration) | Tries to load the image from the cache and if it does not exist, then download it from the internet. Of course, obeying the fetchAlways boolean. |
These are pretty awesome functions too, and they’re straightforward.
We’re going to download either an array of bytes, as in the first function, or a cross platform IBitmap in the second function.
var talkingCats = await BlobCache.LocalMachine.LoadImageFromUrl ("https://catimages.com", false);
Deleting Objects
InvalidateObject(key) | Removes object corresponding to the key with type of T from the cache |
---|---|
InvalidateAllObjects() | Removes all objects of type T from the cache |
Vacuum | Removes all expired objects from the cache |
Maintenance functions – not the most glamorous, but still necessary (and hopefully self-explanatory).
Conclusion
Akavache – our new most awesome friend in the world! Taking all of the tedious maintenance out of implementing caching plumbing so we can concentrate on creating great apps for our users.
Akavache employs a SQLite backend to store key/value pairs of objects. The value portion of those objects are stored in BLOB fields so they can hold anything from a plain POCO object to the most complex object hierarchy. Akavache gives us an easy to use API to store, delete and retrieve objects from the cache, including some sweet functions to automatically download info if it doesn’t exist and update the cache with the latest info.
So fire up Xamarin Studio or head on out to Github or NuGet and see for yourself why Akavache is Also Known As Awesome!