Thursday, March 07, 2013

Roaming data with ApplicationData

Brain Dead Simple Windows RT App Part 3

In Window 8, you should be able to move between your devices and your apps should follow you. In the Microsoft documentation, there is a lot of talk about some office worker who likes to start some task on her office computer (possibly when her boss’s back is turned) and finish them on the bus with her tablet. The feature that they are pushing with these examples is Roaming.

So by the time that Emily (we can call her that) turns off her computer at work, the app needs to save some state data to the cloud, when she opens the same app on her tablet, it goes out and gets the that data from the cloud and uses it to remember where she left off.

All the stuff you need to do this is in Windows.Storage.ApplicationData. You have a choice of where you want to save it (Roaming or Local) and how (ApplicationDataContainer or StorageFolder).

Roaming data is first stored locally and then pushed to Microsoft servers on the cloud; there is a limit to how much data can be roamed, you can use RoamingStorageQuota to find out how much (note that if you exceed the quota, it will just fail without any errors at all).

Local stores the data locally so no roaming occurs but you have more space to work with and can be used for things that aren’t appropriate to roam.

ApplicationDataContainer (used for RoamingSettings & LocalSettings) contains a property called Values which is a dictionary that contains the settings. Each value in the dictionary is handled separately, so one value may be roamed before the other. You can use ApplicationDataCompositeValue to stick values together and they will be treated as a single unit. Oh, yea, there is a special value in the dictionary called “HighPriority” that will be roamed before everything else.

StorageFolder (used for RoamingFolder, LocalFolder & TemporaryFolder) gives you a handle to a folder in the file system that you can use to read and write files. Files in RoamingFolder will be synced with the magic Microsoft server (if the total size doesn’t exceed RoamingStorageQuota. Files in LocalFolder are not synced but will stay around for a while; the files in TemporaryFolder don’t.

Back to the App

When I run my Hello app, I can press the Exclamation Point Button 42 times but when I close it down and open it up again, if forgets. So, when I open the app again, there are NO exclamation points and that dampens my enthusiasm; I want to keep my excitement so the exclamation points need to stay. We can solve this problem with Roaming!

 

// I replace most refrence to BASE_MESSAGE to _baseMessage
private string _baseMessage = BASE_MESSAGE;

// Ensure that RoamingSettings has been loaded
private static ApplicationDataContainer _roamingSettings;
public static ApplicationDataContainer RoamingSettings
{
    get
    {
        if (_roamingSettings == null)
        {
            //If I don't want roaming, use ApplicationData.Current.LocalSettings;
            _roamingSettings = ApplicationData.Current.RoamingSettings;
        }
        return _roamingSettings;
    }
}

// I know that saving every time the value is changed,
// in real life I'd save the data in App.OnSuspending()
private void saveSettings()
{
    // I combine both values into a ApplicationDataCompositeValue
    // and save them both to "HighPriority"
    var composite = new ApplicationDataCompositeValue();
    composite["baseMessage"] = _baseMessage;
    composite["exlamationPointCount"] = _exlamationPointCount;
    RoamingSettings.Values["HighPriority"] = composite;
}
// To be called from the constructor
private void loadSettings()
{
    // I get "HighPriority" cast it to ApplicationDataCompositeValue
    // and extract the values.
    var composite = (ApplicationDataCompositeValue)
    RoamingSettings.Values["HighPriority"];
    if (composite != null) // the first time it will be null
    {
        _baseMessage = (string)composite["baseMessage"];
        _exlamationPointCount = (int)composite["exlamationPointCount"];
    }
}

And I change the constructor to look like:

public MainPage()
{
    this.InitializeComponent();

    // Load Settings here:
    loadSettings();
    HelloMessage = setMessge(_exlamationPointCount);
    this.DataContext = this;
}

And the setMessage() to the field instead of the constant:

private string setMessge(int exlamationPointCount)
{
    if (exlamationPointCount > 0)
    {
        return _baseMessage + new string('!', exlamationPointCount);
    }
    else
    {
        return _baseMessage;
    }
}

Since I haven’t changed the markup, I won’t repost that part (see my previous post), but I will repost the code behind (MainPage.xaml.cs):

using System.ComponentModel;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace HelloWindows
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private const string BASE_MESSAGE = "Hello Windows";

        public MainPage()
        {
            this.InitializeComponent();

            // Load Settings here:
            loadSettings();
            HelloMessage = setMessge(_exlamationPointCount);
            this.DataContext = this;
        }


        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new  PropertyChangedEventArgs(propertyName));
            }
        }

        public string HelloMessage { get; set; }


        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  
        /// The Parameter property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private int _exlamationPointCount = 0;

        private string setMessge(int  exlamationPointCount)
        {
            if (exlamationPointCount > 0)
            {
                return _baseMessage + new string('!', exlamationPointCount);
            }
            else
            {
                return _baseMessage;
            }
        }

        private void addPoint_Click(object      sender, RoutedEventArgs e)
        {
            HelloMessage = setMessge(++_exlamationPointCount);
            RaisePropertyChanged("HelloMessage");
            RaisePropertyChanged("IsRemovePossible");
            // This is overkill
            saveSettings();
        }

        private void removePoint_Click(object sender, RoutedEventArgs e)
        {
            if (_exlamationPointCount >= 1)
            {
                HelloMessage = setMessge(--_exlamationPointCount);
                RaisePropertyChanged("HelloMessage");
                RaisePropertyChanged("IsRemovePossible");
                // This is overkill
                saveSettings();
            }
        }

        // Make it possible to disable Remove Point button when there
        // is nothing left to remove
        public bool IsRemovePossible
        {
            get { return _exlamationPointCount > 0; }
        }
        // I replace most refrence to BASE_MESSAGE to _baseMessage
        private string _baseMessage = BASE_MESSAGE;

        // Ensure that RoamingSettings has been loaded
        private static ApplicationDataContainer _roamingSettings;
        public static ApplicationDataContainer RoamingSettings
        {
            get
            {
                if (_roamingSettings == null)
                {
                    // If I don't want roaming, use 
                    // ApplicationData.Current.LocalSettings;
                    _roamingSettings = ApplicationData.Current.RoamingSettings;
                }
                return _roamingSettings;
            }
        }

        // I know that saving every time the value is changed,
        // in real life I'd save the data in App.OnSuspending()
        private void saveSettings()
        {
            // I combine both values into a ApplicationDataCompositeValue
            // and save them both to "HighPriority"
            var composite = new  ApplicationDataCompositeValue();
            composite["baseMessage"] = _baseMessage;
            composite["exlamationPointCount"] = _exlamationPointCount;
            RoamingSettings.Values["HighPriority"] = composite;
        }
        // To be called from the constructor
        private void loadSettings()
        {
            // I get "HighPriority" cast it to ApplicationDataCompositeValue
            // and extract the values.
            var composite = (ApplicationDataCompositeValue)
            RoamingSettings.Values["HighPriority"];
            if (composite != null           ) // the first time it will be null
            {
                _baseMessage = (string)composite["baseMessage"];
                _exlamationPointCount = (int)composite["exlamationPointCount"];
            }
        }
    }
}

My Brain Dead Simple Windows RT App: The Series

No comments: