Skip to main content
May 3, 2016
Mobile

Getting Started with Roaming App Data



Users today are mobile, transitioning from one device to the next throughout the day. Increasingly, these same users expect (or even demand) to take their data with them. Fortunately for us, roaming app data makes this a reality.

Today, we kick off a two-post series that explores how to use roaming app data to give your users a truly mobile experience. In today’s blog post, we’ll explain what roaming data is, how it works, and some ways you can use it in your own app. Finally, we’ll also cover versioning and conflict resolution.

Roaming app data

Roaming app data is the way in which all Universal Windows Platform (UWP) apps keep data in sync across multiple devices. It allows you, the developer, to create apps that help users carry data such as user profiles or documents from one device to the next. Generally speaking, roaming app data breaks down into three main categories:

  • App data is the data your application requires to function. This data needs to be in sync across all devices.
  • User data is any data that the user initiates via the application that will synchronize across all devices. For example, if you use your Microsoft account to store Microsoft Office files in the cloud from your desktop and then open Microsoft Office on your laptop, you will be presented with a list of your recent files.
  • App settings are any configuration settings for an app that will stay in sync across all devices. Think of your profile settings in Visual Studio 2015.
1_RoamingAppData

Before we get started digging into the API, it’s important to understand that you have two main points of entry into your roaming app data:

RoamingSettings

The built-in ApplicationData.RoamingSettings property, which is of the type ApplicationDataContainer, stores its data as a dictionary key/value pair with a string for the key. The key of each setting can be up to 255 characters long and the value for each setting can be no more than 8K bytes in size. Bear in mind, however, that you can only store the following data types:

  • UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
  • Boolean
  • Char16, String
  • DateTime,TimeSpan
  • GUID, Point, Size,Rect
  • ApplicationDataCompositeValue

Although you should choose the data type that works best for you and your specific app, ApplicationDataCompositeValue in the above list is the recommended choice. Unlike the other types, which have an 8k-byte limit, this type allows you to store up to 64K bytes. Critically, it also lets you store a subset of settings or categorize your settings.

Our first step in implementing the API is to create a variable referencing RoamingSettings to perform all of our data operations:

[code language=”csharp”]

ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;

[/code]

With the roamingSettings variable created, it is relatively straightforward to then write a value to it, like so:

[code language=”csharp”]

roamingSettings.Values["lastViewed"] = DateTime.Now;

[/code]

It is just as straightforward to read this data from the setting:

[code language=”csharp”]

DateTime lastViewed = (DateTime)roamingSettings.Values["lastViewed"];

[/code]

Keeping the same roamingSettings variable we used above, and assuming you did choose ApplicationDataCompositeValue for your data type, let’s look at how easy it becomes to then group and organize some theoretical roaming app data:

[code language=”csharp”]

ApplicationDataCompositeValue bookComposite = new ApplicationDataCompositeValue();
bookComposite["lastViewed"] = DateTime.Now;
bookComposite["currentPage"] = 32;
roamingSettings.Values["bookCompositeSetting"] = bookComposite;

[/code]

We can read the data out from these composite values just like we have done before:

[code language=”csharp”]

ApplicationDataCompositeValue bookComposite = (ApplicationDataCompositeValue)roamingSettings.Values["bookCompositeSetting"];
DateTime lastViewed = (DateTime)bookComposite["lastViewed"];
int currentPage = (int)bookComposite["currentPage"];

[/code]

The beauty of this simple API is that it empowers the developer to build rich user experiences across devices with just a little bit of boilerplate code.

Tip: Be advised, there is no direct way for you to trigger a sync from one device to another. This is handled by the OS the device is running.

RoamingFolder

We’ve looked at RoamingSettings and how to store your data using it, but what about non-structured, data-like files? And what if the data type limitations of RoamingSettings are just too restrictive for what you want to accomplish? This is where RoamingFolder (of type StorageFolder) comes in handy.

Let’s use a real-world example of a To-Do app to show how this works. Assume that for the purposes of this app, it is critical to sync all of your users’ to-do items across all of their devices.

First, you need to provide a filename for storing the data—use “todo.txt”:

[code language=”csharp”]

StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder;
var filename = "todo.txt";

[/code]

Then, write a Todo class that allows you to keep all information for a given to-do item organized together. It has a Task and an IsCompleted property:

[code language=”csharp”]

class Todo
{
public string Task { get; set; }
public bool IsCompleted { get; set; }
}

[/code]

Next, you will want to create a function to write out to-do items—call it WriteTodos(). Build the to-do items and serialize them as a JSON string using the Newtonsoft.Json library. Create the file asynchronously, overwriting it if it already exists. Finally, write out the text—also asynchronously:

[code language=”csharp”]

async void WriteTodos()
{
var todos = new List<Todo>();
todos.Add(new Todo() { Task = "Buy groceries", IsCompleted = false });
todos.Add(new Todo() { Task = "Finish homework", IsCompleted = false });
string json = JsonConvert.SerializeObject(todos);
StorageFile file = await roamingFolder.CreateFileAsync(filename,
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, json);
}

[/code]

Let’s see what it takes to read the file you created by creating a ReadTodos() function. You retrieve your StorageFile asynchronously and then deserialize the string as a List<Todo> object. And that’s all there is to it.

[code language=”csharp”]

async void ReadTodos()
{
try
{
StorageFile file = await roamingFolder.GetFileAsync(filename);
string json = await FileIO.ReadTextAsync(file);
List<Todo> todos = JsonConvert.DeserializeObject<List<Todo>>(json);
// Perform any operation on todos variable here.
}
catch (Exception ex)
{
// Handle exception…
}
}

[/code]

Keep in mind that even if you place some files in the RoamingFolder, they may not roam if they are…

  • file types that behave like folders (e.g. files with .zip and .cab extensions)
  • files that have names with leading spaces
  • file paths that are longer than 256 characters
  • files that are empty folders
  • files with open handles

Constraints

Although we’ve covered how to use roaming app data with RoamingSettings and RoamingFolder, there are still a couple of constraints to take into account. First, it is important to remember that in order for roaming app data to work, users need to have a Microsoft account and use this same account across all devices.

Next, Microsoft account users receive a specific quota for storage, accessed through the ApplicationData.RoamingStorageQuota property (currently, this is 100KB). Once a user has reached their storage limit for a given app, all roaming will cease to work until data is removed from roaming app data.  A good rule of thumb to help your users avoid this experience is to focus on user preferences, links, and small data files for roaming data and lean on local and temporary data for everything else.

In case you need to remove settings and files, you’ll want to call the Remove function and pass in the key:

[code language=”csharp”]

roamingSettings.Values.Remove("lastViewed");

[/code]

You can even go a step further if you want to remove our composite value container completely:

[code language=”csharp”]

roamingSettings.DeleteContainer("bookCompositeSetting");

[/code]

You may want to provide custom data features that go beyond the constraints of roaming app data. In this case, roaming data associated with a Microsoft account may not be the best implementation. Instead, you may want to consider Microsoft Azure or another service to provide the same roaming user experience.

Syncing and Versioning your app data

Now that you’ve implemented the API and understand its constraints, let’s explore two critical features of roaming app data in greater depth—syncing and versioning:

  • Syncing is the means by which changes on one device are transmitted to the cloud for use by another device.
  • Versioning provides you, the developer, with the ability to change the structure of the data that is stored on each device. In turn, this allows you to incrementally roll-out versions of the data structure so that the end-user has a reduced chance of a poor experience.

Syncing conflicts

In talking about data syncing, it’s important to also talk about data conflicts. Consider the following example:

A user opens your task application on his desktop and starts creating a list. At the same time, the user logs onto another PC with the same account, opens your application and continues to work on the list. When the user goes back to the original PC, what is the expected behavior?

In this example, as long as the underlying roaming app data files are released and not still open, the sync will happen when the changes occur. The conflict policy for syncing is simple: the last writer wins.

It is also possible to know when a sync occurs at runtime on a given device. Simply wire up the ApplicationData.DataChanged event and you will be notified when a sync happens:

[code language=”csharp”]

private void HandleEvents()
{
ApplicationData.Current.DataChanged += Current_DataChanged;
}
void Current_DataChanged(ApplicationData sender, object args)
{
// Update app with new settings
}

[/code]

Versioning

The nice thing about app data versioning is that as your application matures, your app data structure may change as well. However, always bear the following in mind:

  • Your user could be several versions back on one device and current on another device
  • App data versions apply to all state information managed via the ApplicationData class
  • No relationship to the application version; many application versions can and will likely use the same app data version
  • App data version numbering always starts at zero (0)

It is recommended that you use increasing version numbers as you roll out new releases. You simply call ApplicationData.SetVersionAsync, passing in a callback to handle any migrations from an older version to the current one. We initiate a SetVersionAsync by specifying the version number and also providing a callback. In the callback, we evaluate the current local version and apply any migration logic as necessary. This approach is very similar to EntityFramework CodeFirst migrations. The following snippet provides logic to handle multiple version changes in case the user has not used the application on a given device for a while.  One possible way to upgrade your app data between versions is to loop through the entire upgrade cycle.  This ensures the upgrade permutations are minimal.

[code language=”csharp”]

ApplicationData appData = ApplicationData.Current;

// nice friendly reminder of when you last updated
// Version 2 – 2016.02.29
const uint currentAppDataVersion = 2;

void UpdateAppDataVersion(SetVersionRequest request)
{
SetVersionDeferral deferral = request.GetDeferral();
uint version = appData.Version;

while (version < currentAppDataVersion)
{
switch (version)
{
case 0:
// changes needed to move to version 1
// This sample simulates that conversion by writing a version-specific value.
appData.LocalSettings.Values[“favoriteBand”] = randomBand();
appData.LocalSettings.Values[“location”] = “Chicago”;
break;
case 1:
// determined we can do location way better by storing lat, long instead;
// magic function that does null checks and everything 🙂
appdata.LocalSettings.Values[“locationGeo”] =
convertStringLocationToGeo(appdata.LocalSettings.Values[“location”]);
break;
case 2:
// up to date, no need to do anything
break;
default:
throw new Exception("Unexpected ApplicationData Version: " + version);
}
version++;
}

deferral.Complete();
}

async void SetVersion1_Click(Object sender, RoutedEventArgs e)
{
await appData.SetVersionAsync(currentAppDataVersion,
new ApplicationDataSetVersionHandler(UpdateAppDataVersion));
}

[/code]

Testing

Developers can lock their device in order to trigger a synchronization of roaming app data. If it seems that the app data does not transition within a certain timeframe, please check the following items and make sure that:

  • Your roaming app data does not exceed the RoamingStorageQuota
  • Your files are closed and released properly
  • There are at least two devices running the same version of the app
  • Roaming has not been disabled via a device policy or roaming has been manually turned off

Also, be aware that roaming app data syncing doesn’t happen immediately. There will be some degree of latency between the change you make on one device and when it shows up on another.

Wrapping Up

In this post, we have looked at how to implement roaming app data and explored how it operates. Stay tuned for our next, which will examine further the ins and outs of synchronization, including sync of different data types, first data load, handling offline scenarios, and resolving conflicts.

In the meantime, feel free to download the samples and start playing!

Additional Resources