Who Turned Off the Lights? Azure Offline Editing and Syncing

We spent the last post cloud gazing and exploring the Azure Mobile Services API as it relates to creating and retrieving data from a Xamarin mobile app. But we were limited to accessing our data only when connected to the internet.

What happens when the skies turn dark, a storm rolls in and we lose internet connectivity? Or, in our case with the Cheesed app we are developing, our users are deep in a cheese cave sampling a rare 20 year cheddar and their mobile device cannot reach the internet?

Do we just display an error message saying, “stop eating cheese … no internet, try again later”? And not let them enter a rating or any notes on the complexities of the cheese?

That’s not acceptable! How can we expect somebody to use an app, but only when they are online? Lucky for us, Azure Mobile Services provides offline capabilities! So let’s dig in and see what it’s all about.

Benefits of Working Offline

Being able to edit data anytime is only one benefit that the offline capabilities that Azure Mobile Services gives us.

Other benefits include:

  • Resiliency against network outages (we’ll have a data cache that will always be there).
  • Improves the (perceived) performance of the app – if we design it right, we can have the data already downloaded and onboard the device before the user needs it.
  • Detect conflicts when the same record modified on more than one device and then sync’d/pushed to the cloud.

We’ll talk about how to handle conflicts in the next post – but for now let’s dig into how to make our app offline capable using the Azure Mobile Services library.

Basic Setup

Installation

Just about everything we need to implement offline capability is included in the same NuGet package we used in the last post, WindowsAzure.MobileServices.

Install-Package WindowsAzure.MobileServices

BUT… the one thing that isn’t included in that package is the offline repository or database itself. The package above contains everything we need to do push and pull from the cloud, but does not contain an implementation of how to store the data on the device in between.

Luckily, we don’t have to roll our own repository as Microsoft has provided one for us, and it’s based on SQLite (man – that SQLite just keeps popping up every where on mobile devices, doesn’t it?).

It’s named WindowsAzure.MobileServices.SQLiteStore and it gives us a way to store and edit data locally before we push it to the cloud.

To install, run the following NuGet command.

Install-Package WindowsAzure.MobileServices.SQLiteStore

Of course, if we wanted to implement our own local storage we could, but for the purposes of this post, we’re going with Microsoft’s.

Modeling Data

Modeling data to be included in offline editing is no different than discussed in the previous post on modeling data for Azure. We just use POCO classes, matching the class and property names to the table and column names in Azure.

Or we can use the Newtonsoft.Json.JsonPropertyAttribute to map class and property names to table and column names respectively. See the last post for an in-depth discussion.

How It Works

The basics of offline data editing with Azure are as follows:

  • All data CRUD operations takes place against a local SQLite database.
  • Data only sent to the cloud when “pushed”.
  • Data for a specific table retrieved from the cloud when “pulled”. A pull will always automatically initiate a “push” first.

The Push Process

Pushing data to Azure means taking local changes and applying them to Azure. Regardless if we’re online or not, all of the edits to the data occur against a local SQLite database. When we’re ready to “push” to the cloud, only the data that has changed gets pushed. It should be noted that data across all tables will be pushed to Azure (for data consistency), not just data from a single table.

The Pull Process

When we talk about “pulling” data, we mean grabbing data from Azure and bringing it local to the device. The first thing that must happen before we can grab the data from Azure is that we must push any changes we have locally up to the cloud. That means any pull operation automatically invokes a push. This is to keep data in Azure consistent. After data is pushed, then the data we’re requesting is pulled. Note that it’s only data for a single table (and most likely filtered at that) – not all the data in all the tables in Azure.

Working With Offline Data

Basic Initialization

There is some basic boilerplate code that we’ll have to invoke once in an application in order make it work with Azure Mobile Services and also to get the Mobile Services’ SQLite Store to work:

Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init ();
 
SQLitePCL.CurrentPlatform.Init ();

This only needs to be called once in each of your platform specific projects. AppDelegate.cs and MainActivity.cs seem like a good spot to me.

Define Local Storage

Let’s now talk about how we setup Azure Mobile Services to use a local datastore. It’s actually pretty straightforward.

First we create a new MobileServiceSQLiteStore object and pass it the filename of our database. Then we invoke the DefineTable<T> function. This is very much akin to the CreateTable<T> function in SQLite.Net that we talked about in a previous post. Finally, we register our offline data store with the MobileServiceClient . The code will look like the following:

var store = new MobileServiceSQLiteStore ("cheeses.db3");
 
// Do this for every table we want to keep track of locally                 
store.DefineTable<Cheese> ();
store.DefineTable<Dairy> ();
 
await mobileClient.SyncContext.InitializeAsync (store);

Note that we must call the DefineTable<T> function before invoking the InitializeAsync function. It’s that function which actually creates the tables in the SQLite database, so we need to have them defined first. InitializeAsync can accept a second parameter … and this second will help us handle conflicts – which we’ll talk about in the next post.

Setting Up the Sync Tables

Just like in the previous post on working with Azure directly, we still need to have the MobileServiceClient create the table objects that we’re going to work with in order to both persist data locally, and to push and pull it from the cloud.

Instead of invoking GetTable<T> as before, we’ll use GetSyncTable<T> , as the example below illustrates, building from declaring the local store through creating the sync tables:

// Define the local data store
var store = new MobileServiceSQLiteStore ("cheeses.db3");
                    
store.DefineTable<Cheese> ();
store.DefineTable<Dairy> ();
 
await mobileClient.SyncContext.InitializeAsync (store);
 
// Creating the sync tables
var cheeseTable = mobileClient.GetSyncTable<Cheese> ();
var dairyTable = mobileClient.GetSyncTable<Dairy> ();

All of the CRUD operations now will happen against these sync tables – which implement the IMobileServiceSyncTable interface. We can pull (and implicitly push) data from these tables as well. And we can also use the MobileServiceClient to push data as well.

Writing Data

Again, the most important thing to realize is that all writes happen against the local SQLite database. Nothing goes to Azure until we specifically tell it to. That’s the whole point of having offline editing capabilities, right? To allow our users to make any changes they want, whenever they want, no matter their connection status.

InsertAsync , UpdateAsync and DeleteAsync are all defined on the IMobileServiceSyncTable interface. This means that working with the sync tables defined above is the same, and as easy, as before. Nothing special is going on, we’re just modifying the local data store.

As mentioned before, the MobileServiceClient.SyncContext has a PushAsync function on it. This function will take all of the pending changes in the local data store and push them up to Azure.

To add on to the example from above – this time writing data and pushing to Azure:

var store = new MobileServiceSQLiteStore ("cheeses.db3");
    
store.DefineTable<Cheese> ();
store.DefineTable<Dairy> ();
 
await mobileClient.SyncContext.InitializeAsync (store);
 
var cheeseTable = mobileClient.GetSyncTable<Cheese> ();
var dairyTable = mobileClient.GetSyncTable<Dairy> ();
 
var hooksDairy = new Dairy { 
    DairyName = "Hooks"
};
 
var cheddarCheese = new Cheese {
    CheeseName = "20 Year Cheddar",
    CheeseType = "Cheddar",
    Dairy = "Hooks",
    Rating = 5,
    ReviewDate = DateTime.Now
};
        
// Writing data locally
await dairyTable.InsertAsync(hooksDairy);
await cheeseTable.InsertAsync (cheddarCheese);
 
// Push both data changes
await mobileClient.SyncContext.PushAsync();

The sync tables also have a PullAsync function. First this does the exact same thing as the PushAsync function of the MobileServiceClient.SyncContext. Then it issues a query to pull back data related to the table. So it pushes all the data that has been modified since the last push across all the tables, but only pulls back data from a single table.

A couple of notes about pulling data …

If soft deletes have been enabled on the server, then pulling data will delete any records locally which have been marked as a soft delete on the server. If soft deletes are not enabled, the deleted records will stay in local storage, even if they have been deleted from the server, so a call to the sync table’s PurgeAsync function will be necessary from time to time to clean everything up. And of course, PurgeAsync will first push any local modifications before it actually does the purge on the table.

Finally – if a record has multiple transactions against it locally – it was inserted, then updated multiple times – only the final state of the record will be transmitted to Azure on a push. It will not be pushed multiple times. This happens automatically and is nice to save our users some bandwidth.

Reading Data

Obviously when pulling down data from Azure, we do not want to pull all the data from the table. Azure Mobile Services give us some pretty nice functionality to make sure we don’t pull down a ton of data each time, blowing away our users data cap.

Within the PullAsync function, there is a string parameter named “queryId” … by supplying a value for this, the Mobile Service client will keep track of the last time that queryId was issued and then only pull down data changed in Azure since then. It uses the __updatedAt metadata column that Azure automatically puts onto every table.

Mobile Services also lets us pass in either a string query, or a IMobileServiceTableQuery object to further refine the results of the data coming back. For example, to only get the finest cheeses we’ve sampled, the following can be invoked:

await cheeseTable.PullAsync ("deliciousCheese", cheeseTable.Where (ct => ct.Rating > 4));

Again, every time this statement is issued, it will first push all the modified data across every table, but then only bring back cheeses rated “5 wedges” and up since the last time we pulled.

If we just want to query data locally, and not pull it down from Azure, we would do so against the IMobileServiceSyncTable itself, without invoking PullAsync first. The following example illustrates grabbing the best cheeses stored locally:

var justGreatCheese = await cheeseTable.Where(ct => ct.Rating > 4).ToListAsync();

Design Considerations

When designing and developing a mobile app for offline data access, one of the most important things to do is figure out when it makes sense to push data to Azure. Ideally we’d like to bundle logical transactions as best we can – for example, if we have a screen in our app where we create both a cheese and numerous tasting notes for that cheese – and those are stored in different tables – it only makes sense to send all the changes at once.

We want to be sure we keep the data in Azure as logically consistent and complete as we possibly can. Not send bits and pieces up which would impact other users if they issue a pull before all of our logical data is in place.

Summary

In this post we took a look at the benefits of offline data editing and how to implement it using Microsoft Azure Mobile Service’s SQLite Data Store. We learned that modeling data is as simple as POCO objects, that pushing data moves all modified data to the cloud, and pulling data first pushes modifies data to the cloud before it retrieves it. Finally we went through and built up and example of creating the data store, initializing the synchronization context with the mobile service client, and then pushing and pulling data with it.

Next time we’ll look at what happens with conflicts and maybe… just maybe… unveil a functioning Cheesed app!

Featured Image: “100 Wisconsin Avenue, night, Madison, WI” by John Benson from Madison WI – 100 Wisconsin AvenueUploaded by xnatedawgx. Licensed under CC BY 2.0 via Wikimedia Commons