Unplugged Framework for Windows 8.1 Store Apps

Overview

Mobile apps being able to work offline has become an expected feature of almost all mobile/tablet applications. For data-driven applications, it means that developers will have to store user-specific application data locally, and implement a data synchronization mechanism that keeps the local and server data in sync. By incorporating the Unplugged framework into your code, you can extend your Windows 8.1 Applications to download unstructured data and still function when disconnected from the network. This documentation describes the design and features of this framework that you can incorporate into Windows Store applications.

General Concepts

Local Storage & Synchronization Context

All actions that a developer is concerned with reside in the following 3 interfaces. As an app developer, you will primarily interact with the API through these entry points:

  • ISyncContext - Create a context that links the remote and local storage services
  • ISyncFolder - represents a folder entity that is syncronized fro mthe remote service
  • ISyncFile - represents an unstructured entity contained by a folder

When building an application, the code can work with the file instances through the ISyncFile interface. Behind the scenes, file instances delegate work to the associated storage engine to maintain state while offline.

By default, Unplugged uses SQLite as the local database to track the status of files in local folder. This storage is used to compare files when the application is back online.

Class View of Local Storage

Class Diagram

Setup

Software Requirements

To work with the occasionally connected application framework, you must ensure that your system meets the following minimum software requirements:

  1. Windows 8.1 Operating System
  2. Visual Studio 2013
  3. 3rd Party libraries & additional references (set Visual Studio to download required Nuget packages)
    1. SQLite.dll 3.8.4 - Download and install the SQLite Visual Studio extension for Windows 8.1: http://www.sqlite.org/2014/sqlite-winrt81-3080400.vsix
    2. Packages installed via Nuget. This will happen automatically if you have the package manager set up to download dependencies.

Remote Server Setup

In this example, the remote server is responsible for

  1. Providing a SAS for access to your blob storage account
  2. Linking the users device to the blob storage account for that user
  3. Offloading file upload/download directly to the blob storage service

It could be expanded to support distributing users across multiple storage accounts, provide read-only privileges to specific containers, or complete user validation prior to providing a SAS key.

We have included a sample project (TED.WindowsAzure.Unplugged.Mobile.RemoteStorage.AzureBlob) in the toolkit which will help you build a remote file server functionality using Windows Azure blob storage and Mobile Services. If you prefer to use a WebAPI server, you can easily rewrite this script using C# and update WindowsAzureBlobStorage accordingly to call the new API.

In our example we have remote containers of blobs in Windows Azure. We've defined a Windows Azure storage account and secure key in a script running windows Azure Mobile Services. You will need to update the script and keys in the source code with your own windows Azure storage account information.

The script is included in the Script directory of the TED.WindowsAzure.Unplugged.Mobile.RemoteStorage.AzureBlob project

Mobile Services Script Location

You will need to add this script to Windows Azure Mobile Services before the remote storage implementation will work.

Mobile Services Script in Portal

How to Use the API?

Setting Up a New Sync-enabled MobileServiceClient

The central class in the framework is called TEDMobileServiceClient you will need to instantiate TED mobile service client and pass Azure Mobile Services endpoint and application key

var client = new TEDMobileServiceClient(new Uri("<Mobile Services uri>"), "Key");

Then initialize synchronization context. This can be used to plug in additional custom providers.

await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

To separate files into independent areas, the concept of sync folders is implemented. Acquiring a sync folder, is done like this

ISyncFolder folder = await client.SyncContext.GetSyncFolderAsync("MyFiles");

The name (e.g. 'MyFiles') represents the folder name both on the server side as well as the local device side. Once a sync folder is acquired, the framework offers you multiple methods of adding, modifying and synchronizing files.

Syncronizing

The two main operations are pulling down files from the remote storage and pushing changed versions or new files back up. To allow you fine-granular synchronization, the two methods are provided on the level of each individual sync folder:

await folder.PullAsync();
await folder.PushAsync();

Multiple events provide updates on the progress of either push or pull. See API reference for details.

folder.PullStarted += (s, e) =>
{
    Log.Info("Pull started");
};
folder.PullCompleted += (s, e) =>
{
    Log.Info("Pull completed");
};
folder.PullProgressChanged += (s, e) =>
{
    Log.Info(e.RemoteFile + " + " + e.LocalFile);
};
folder.SyncListBuilt += (s, e) =>
{
    Log.Info(e.SyncFiles.Count());
};

Adding a New File

To add a newly created file first you need to create it in the local sync folder:

ISyncFile file = await folder.CreateFileAsync("newfile.txt");

The returned ISyncFile instance is a reference to the entry in the local table of content. Through the object you can next open a WinRT IRandomAccessStream on the file. Since we're using .NET StreamWriter API we first need to convert the WinRT stream to a .NET stream. Then we can for write to the file.

IRandomAccessStream rtStream = (await file.OpenWriteAsync());
Stream = rtStream.AsStreamForWrite();
using (var s = new StreamWriter(stream))
{
    s.Write("Hello World");
}

Finally we need to let the framework know that the updates have been finished

await file.CommitAsync();

During the next push, the new file will be automatically uploaded to the remote storage.

Modifying a Downloaded File Directly

To modify an already downloaded file in the local file system, you need to first get a reference to it by name.

ISyncFile file = await folder.LookupAsync("existingFile.txt");

Once that is done, you can manipulate the file through the stream API similar to the last example.

await file.OpenWriteAsync();
await file.OpenReadAsync();

Don't forget to commit the written changes at the end.

await file.CommitAsync();

During the next push, again the modified file will be automatically uploaded to the remote storage.

Deleting a file

Deleting a file is similar to modifying it. Get a reference to a file first and call delete on it.

ISyncFile file = await folder.LookupAsync("deleteme.txt");
await file.DeleteAsync();

The file will be deleted from the remote storage during the next push.

API Reference

Methods

  • PushAsync - Pushes all locally modified items in the local storage to the associated remote storage.
  • PullAsync - Pulls all remote items from the associated remote storage.
  • ReadAsync - Returns all local files in the LocalStorageFolder
  • LookupAsync - Returns a single or null file based on ID or name
  • FlushAsync - Marks all files which are undefined as ready to Sync
  • CreateFileAsync - Creates a 'undefined' (not inserted) local file based on a user defined name
  • OpenReadAsync - Opens a readable stream containing the specified instance of the file
  • TruncateAsync - Removes all items from the local Folder
  • InsertAndCommitAsync - Insret a file and mark ready to sync
  • UpdateAndCommitAsync - Update a file and mark it ready to sync
  • CancelAsync - Undo all work in progress files

Events

  • PullStarted - Occurs when the pull has started.
  • PullProgressChanged - Occurs every time pull has a progress on one file
  • PullCompleted - Occurs when the pull is completed
  • PushStarted - Occurs when the push has started
  • PushProgressChanged - Occurs when the push is completed
  • SyncListBuilt - Occurs when list of files to be synchronized has been built

Extending the API

Unplugged has been designed to be easily extensible. You can extend the API to meet the requirements of a wide variety of situations. You can easily change local & remote containers as well as SQLite with you own supported local database. The following interfaces are implemented in this project for Windows Azure Storage, Windows 8 file storage, and SQLite. You can reimplement these interfaces to support other platforms as necessary

  • IRemoteFileStorage
  • ILocalFileStorage
  • ISyncFileStateStorage

Limitations

The framework still has some rough edges and needs some time to address these limitations:

  1. Multiple users reading and writing the files at same time will have undesirable effects. The server does not implement any history or versioning so last update wins.
  2. Limited conflict resolution - We are evaluating the use of sync policies to define how the API will react when synchronizing data changes. However, there is no current implementation in place
  3. Manual Pull/Push only - There is no automatic synchronization available. The app must call pull() and push() at regular intervals
  4. No notification of changes - It is up to the developer to implement a notification system for the app as it is not currently built into the framework

Final Words

The code base for this framework should be considered a 0.5 release. We are releasing this sample code to the community to get early feedback about important offline features based on developer demand. Regardless, this code sample is definitely usable as a starting point for new projects that require syncronization of unstructured data between the app and a backend service.

Last edited Mar 27, 2014 at 5:54 PM by mjz115, version 6