Mobile Corner

Build Network-Aware Windows Phone Apps with Azure Mobile Services and Background Tasks

Here's how to build a Windows Phone application that synchronizes data with Azure Mobile Services, triggered when the device restores its network connection.

We are living in an increasingly ever-connected technology landscape, but a lot of enterprise applications still need to deal with being occasionally connected -- meaning, they're designed to work with or without a network connection. For these scenarios, data has to be synchronized down and cached within the application and, similarly, any data captured while offline has to be replayed to the server when the connection is restored.

This article covers two key enablers for these scenarios: caching and synchronizing incremental data from Microsoft Azure Mobile Services (AMS), and being able to trigger background synchronization when network availability changes.

To get started, I'll create a new service using the following steps:

  1. Log in to the Azure Management Portal at manage.windowsazure.com.
  2. Select the Mobile Services tab.
  3. Select New | Mobile Service | Create.
  4. Specify the details of the new Azure Mobile Service (as shown in Figure 1):
    • URL: syncinbackground
    • Database: Create a free 20MB SQL Database
    • Region: West US
    • Backend: .NET
[Click on image for larger view.] Figure 1. Creating a Mobile Service in Microsoft Azure Mobile Services

At this point, I'll accept the remaining defaults to complete the creation of the service:

  1. From the Quick Start pane (the default after creating the new service) choose Windows as the Platform and then expand the link to Create a New Windows or Windows Phone App.
  2. Click the large green Download button.
  3. Unblock and extract the contents of the downloaded .zip file.
  4. In the extracted files, locate the syncinbackground.sln file and double-click it in order to launch Visual Studio.
  5. In the Solution Explorer, right-click on the syncinbackground project file and select Publish.
  6. Sign in using a Microsoft Account and select the syncinbackground AMS.
  7. Step through the remaining steps and click Publish to upload and publish the AMS.

The downloaded solution also includes a Universal application made up of both Windows Store and Windows Phone projects, along with the accompanying shared project. Before making changes to these applications to synchronize data for offline access, it's worth verifying that they function correctly. In the App.xaml.cs file, located in the shared project, I need to comment out the declaration of the MobileService variable that references "localhost," and uncomment the alternate declaration, which references the newly deployed AMS.

The declaration should look similar to the following:

public static MobileServiceClient MobileService = new MobileServiceClient(
  "https://syncinbackground.azure-mobile.net/",
  "<application key>"
);

Both Windows Store and Windows Phone applications can be run at this point and can be used to create tasks that will be saved directly to the AMS. The tasks will also be loaded from the AMS and displayed on the screen.

At the time of this writing, the synchronization support for AMS is still in beta, so in order to add support, the prerelease version of AMS needs to be referenced. From the NuGet Package Manager, "Include Prerelease" is selected from the first filter dropdown, and "Azure Mobile" is entered into the search filter. Windows Azure Mobile Services is selected from the search results (as shown in Figure 2) and Update is clicked.

[Click on image for larger view.] Figure 2. Updating the Windows Azure Mobile Services SDK

To ensure all projects are updated, I open the NuGet Package Manager by right-clicking on the Solution node in Solution Explorer and selecting Manage NuGet Packages for Solution.

In addition to updating the AMS NuGet package I also need the prerelease Azure Mobile Services SQLiteStore package, which will enable the application to persist data using SQLite. This package only needs to be installed into the Windows Store and Windows Phone projects. Adding this package may cause build errors:

  • It relies on the SQLite for Windows Runtime (WinRT for Windows 8.1) and the SQLite for Windows Phone 8.1 extensions (both can be downloaded from sqlite.org/download.html).
  • The version of the SQLite extension may differ from those that are added as references when adding the NuGet package. To resolve these, the reference to the SQLite extension should be removed from the relevant project and the correct version added using the Add Reference dialog.
  • The SQLite dependency means the applications can't be built for any CPU. As such, the build configuration needs to be switched to x86 or x64

With all the necessary packages referenced it's time to make some changes to the code. The offline model supported by AMS results in minimal changes to the code in order to switch from connecting directly to the services exposed by AMS, to accessing the local offline database. It then introduces an additional step that synchronizes changes between the offline database and the services.

The first change is to update the todoTable variable to reference the new IMobileServiceSyncTable interface:

private IMobileServiceSyncTable<TodoItem> todoTable;

Rather than creating the implementation inline, the initialization code needs to be moved out to a separate method, as it requires the offline database to be created the first time:

private async Task InitializeOfflineMode()
{
  var db = new MobileServiceSQLiteStore("local.db");
  db.DefineTable<TodoItem>();

  await App.MobileService.SyncContext.InitializeAsync(db, new MobileServiceSyncHandler());

  todoTable = App.MobileService.GetSyncTable<TodoItem>();

}

As you can see in the code, the InitializeOfflineMode method creates an offline database called local.db, which defines a table for the entity type TodoItem. The offline database is used to initialize the synchronization context for the AMS. Finally, the todoTable is instantiated by calling GetSyncTable, rather than GetTable, which returns a reference to the corresponding service. The InitializeOfflineMode method needs to be invoked at the beginning of the RefreshItems method to ensure the offline database has been created:

private async Task RefreshTodoItems()
{
  if (todoTable == null)
  {
    await InitializeOfflineMode();
  }
  ....

In order for the application to be able to synchronize change for TodoItem entities, some additional properties need to be added that allow for changes to be tracked. Rather than add these directly to the TodoItem, a base class, EntityData, is introduced, which allows for reuse across future types that might need to be synchronized:

public class EntityData
{
  public string Id { get; set; }


  [CreatedAt]
  public DateTimeOffset CreatedAt { get; set; }

  [UpdatedAt]
  public DateTimeOffset UpdatedAt { get; set; }

  [Version]
  public string Version { get; set; }
}

As the EntityData type has an Id property, when the TodoItem class is updated to inherit from EntityData, the Id property also needs to be removed:

public class TodoItem:EntityData
{
  [JsonProperty(PropertyName = "text")]
  public string Text { get; set; }

  [JsonProperty(PropertyName = "complete")]
  public bool Complete { get; set; }
}

At this point, the application can be run. No items will appear initially, but when items are added they'll appear in the list, and restarting the application will show the added items. This works because it is using the offline database to save and refresh items. In fact you may even notice the application runs faster because it's only using the offline database, rather than making service calls.

The final step is to integrate with the services to allow the data to be synchronized. As the AMS synchronization support is currently in prerelease, there's an issue with the synchronization logic, which will result in the most recent item always being downloaded each time synchronization is invoked. To avoid this, the extension methods in Listing 1 need to be added.

Listing 1: Extension Methods to Fix Synchronization Logic
public static class MobileServiceClientExtensions
{
  public async static Task PullLastestAsync<TTable>(
    this IMobileServiceClient client) where TTable : EntityData
  {
    // Get the most recent
    var mostRecent = await client.GetLatestAsync<TTable>();

    // Convert the most recent into a query (assuming there is one)
    if (mostRecent != null)
    {
      var maxTimestamp = mostRecent.UpdatedAt.AddMilliseconds(1);
      var q = client.GetSyncTable<TTable>()
        .CreateQuery()
        .Where(x => (x.Id != mostRecent.Id || x.UpdatedAt > maxTimestamp));
      // Do a (filtered) pull from the remote tabl
      await client.GetSyncTable<TTable>().PullAsync(typeof(TTable).Name, q);
    }
    else
    {
      await client.GetSyncTable<TTable>().PullAsync(typeof(TTable).Name, 
        client.GetSyncTable<TTable>().CreateQuery());
    }
  }

  public async static Task<TTable> GetLatestAsync<TTable>(
    this IMobileServiceClient client) where TTable : EntityData
  {
    return (await client.GetSyncTable<TTable>()
                        .OrderByDescending(x => x.UpdatedAt)
                        .Take(1)
                        .ToListAsync()).SingleOrDefault();
  }
}

The MainPage of the application needs an extra button to allow for triggering synchronization, which can be added below the Refresh button:

<Button Click="ButtonSync_Click">Sync</Button>

The event handler will invoke the Synchronize method before refreshing the list of items on the screen:

private async void ButtonSync_Click(object sender, RoutedEventArgs e)
{
  await Synchronized();
  await RefreshTodoItems();
}

private async Task Synchronized()
{
  await App.MobileService.PullLastestAsync<TodoItem>();
}

When the application runs, it will show items that are available in the offline database. Clicking the Sychronize button will both push up the local changes to the AMS services, as well as download any items that haven't been synchronized into the offline database.

Part of the challenge with applications that are designed to work offline is ensuring that changes made offline are synchronized when the device comes back online. Relying on user behavior to remember to open the application and click the Synchronize button isn't good enough because users are seldom that reliable. Luckily, with the addition of a background task, synchronization can be automatically triggered as soon as the device goes online.

To add a background task, the application needs to reference a separate WinRT component. In the Add New Project dialog, the Windows Runtime Component template is selected from under the Universal Apps node, as shown in Figure 3.

[Click on image for larger view.] Figure 3. Creating a Background Tasks Project

A reference to this new project needs to be added to both the Windows and Windows Phone projects. The default Class1 can be renamed to SynchronizationTask and basic synchronization logic can be added (this requires that the BackgroundTasks project references the Azure Mobile Services and SQLiteStore NuGet packages), as shown in Listing 2.

Listing 2: Adding a Reference to the Windows Runtime Component in the Windows and Windows Phone Projects
public sealed class SynchronizationTask : IBackgroundTask
{
  private MobileServiceClient mobileService = new MobileServiceClient(
    "https://syncinbackground.azure-mobile.net/",
    "<application key>"
  );

  public async void Run(IBackgroundTaskInstance taskInstance)
  {
    var deferral = taskInstance.GetDeferral();
    var db = new MobileServiceSQLiteStore("local.db");
    db.DefineTable<TodoItem>();

    await mobileService.SyncContext.InitializeAsync(db, new MobileServiceSyncHandler());

    await mobileService.PullLastestAsync<TodoItem>();

    deferral.Complete();
  }
}

The synchronization logic requires access to the TodoItem, EntityData and MobileServiceClientExtensions classes. As the BackgroundTasks project is a WinRT component, these classes can't just be moved to that project and accessed by the Windows and Windows Phone projects. Instead, it's necessary to create an additional class library (SyncInBackground.Core), which can house these classes. This project is then referenced by the BackgroundTasks project and the Windows and Windows Phone projects.

For the SynchronizationTask to be executed when the Internet is available it needs to be registered as a background task. There are two parts to this: declaring the background task in the manifest file and then registering the background task when the application is launched. Figure 4 and Figure 5 show the declaration of the background task in the Declarations tab of the Project properties pane (accessible by double-clicking the Properties node under the respective project in Solution Explorer). Note that for Windows, the Lock screen notifications property under the Application tab needs to be set to Badge or Badge and Tile Text, and the Badge logo under the Visual Assets tab needs to be set to a white on transparent image.

[Click on image for larger view.] Figure 4. Declaring a Background Task for Windows
[Click on image for larger view.] Figure 5. Declaring a Background Task for Windows Phone

When the application is launched, the background task can be registered in the OnNavigatedTo method, prior to loading the items on the screen, as shown in Listing 3.

Listing 3: Registering the Background Task in the OnNavigatedTo Method
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
  await RegisterBackgroundTask();

  await RefreshTodoItems();
}

private const string SyncTaskName = "Synchronization";
private async Task RegisterBackgroundTask()
{

  var hasAccess = await BackgroundExecutionManager.RequestAccessAsync();
  if (hasAccess == BackgroundAccessStatus.Denied) return;

  var builder = new BackgroundTaskBuilder
  {
    Name = SyncTaskName,
    TaskEntryPoint = typeof (SynchronizationTask).FullName
  };

  builder.SetTrigger(new SystemTrigger(SystemTriggerType.InternetAvailable, false));
  builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
  var task = builder.Register();
  task.Completed+=BackgroundSynchronizationCompleted;
}

private void BackgroundSynchronizationCompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
  Dispatcher.RunAsync(CoreDispatcherPriority.Normal,()=> RefreshTodoItems());
}

This code not only sets up the background task to be triggered when the InternetAvailable system attribute changes, it also adds a constraint that the task should only be invoked when the Internet is available. It also wires up a Completed event handler that calls the RefreshTodoItems method to update the list of items on the screen, should the application be running when the background task completes.

The applications on Windows and Windows Phone can be run as usual. However, attempting to trigger the background task during debugging by changing the network availability of the test device won't invoke the background task. Instead, in the background, task can be invoked from the Lifecycle Events dropdown in Visual Studio, as shown in Figure 6. Note that the name of the task in the dropdown is the name used when registering the background task in code. In this case, the name is "Synchronization."

[Click on image for larger view.] Figure 6. Invoking Synchronization Within Visual Studio

This is a limitation imposed during debugging only; when the application is deployed and run on a device, the background task will be invoked as expected when the Internet becomes available.

I've shown how to get started accessing data via the Azure Mobile Services SDK and how background tasks can be used to ensure data is synchronized as soon as the Internet is accessible. This is the foundation for a lot of enterprise- and business-quality applications and should be a focus for mobile and desktops applications wanting to optimize both UX and offline accessibility.

comments powered by Disqus

Featured

Subscribe on YouTube