Friday 30 January 2015

Local, Roaming & Temporary Files

Previously we looked at storing data in the the local and roaming settings which basically amounted to storing key value pairs in dictionaries, really easy to work with, but limited and not suited for user data, but rather appropriate for settings.

To save real data we should create local files. We can do this in the following manner

async void CreateFile()
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    StorageFile localFile = await localFolder.CreateFileAsync("MyFile.txt", CreationCollisionOption.GenerateUniqueName);

    await FileIO.WriteTextAsync(localFile, "Hello World");
    var localPath = localFolder.Path;

}

really easy, now I added a localPath variable at the end so that you could create a break point and inspect where you`re file is getting saved to.

let`s take a look, if we navigate to that directory our MyFile.txt will be there.

C:\Users\Administrator\AppData\Local\Packages\bb5297f1-72d9-418c-a1ec-901850673c50_75cr2b68sm664\LocalState

Ok Next let`s try and read our file

async Task<string> ReadFromFile()
{
    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
    StorageFile localFile = await localFolder.GetFileAsync("MyFile.txt");
    return await FileIO.ReadTextAsync(localFile);

}

and that`s it you`re writing and reading to a file, and as before with just changing LocalFolder To RoamingFolder your file is now roaming with your user account.

async void CreateFile()
{
    StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder;
    StorageFile roamingFile = await roamingFolder.CreateFileAsync("MyFile.txt", CreationCollisionOption.GenerateUniqueName);

    await FileIO.WriteTextAsync(roamingFile, "Hello World");
}

async Task<string> ReadFromFile()
{
    StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder;
    StorageFile roamingFile = await roamingFolder.GetFileAsync("MyFile.txt");
    return await FileIO.ReadTextAsync(roamingFile);

}

could it be any easier, I dare to say i think not

and of coarse as promised saving temporary files.

async void CreateFile()
{
    StorageFolder roamingFolder = ApplicationData.Current.TemporaryFolder;
    StorageFile roamingFile = await roamingFolder.CreateFileAsync("MyFile.txt", CreationCollisionOption.GenerateUniqueName);

    await FileIO.WriteTextAsync(roamingFile, "Hello World");
}

async Task<string> ReadFromFile()
{
    StorageFolder roamingFolder = ApplicationData.Current.TemporaryFolder;
    StorageFile roamingFile = await roamingFolder.GetFileAsync("MyFile.txt");
    return await FileIO.ReadTextAsync(roamingFile);

}

just as before

Monday 26 January 2015

Local & Roaming Settings

In essence there's two types of data in an application, that is User Data and Application Data. The distinctions being:
  • Application Data: Data relevant to the application, such as app settings, or context specific information such as for example in a bus route application the location of bus stops or the collection of routes.
  • User Data: for example in a fitness application that tracks exercise the exercise would be Application Data, but the values would be User Data, because they're only relevant to the user. 
You could make the distinction that Application data would be the data that would be used by all users, whereas user data is the data relevant to each particular user of the application.

Let's start with our simplest option, and that is local settings, it`s ideal if you`re looking to store a simple key value pair.

private void SaveValue(string Key, Object Value, string ContainerName = "Main")
{
    var roamingSettings = ApplicationData.Current.RoamingSettings;

    if (roamingSettings.Values.ContainsKey(Key))
        roamingSettings.Values.Remove(Key);
    roamingSettings.Values.Add(new KeyValuePair<stringobject>(Key, Value));
}

private object GetStoredValue(string Key, string ContainerName = "Main")
{
    var roamingSettings = ApplicationData.Current.RoamingSettings;

    if (roamingSettings.Values.ContainsKey(Key))
        return roamingSettings.Values[Key];

    return String.Empty;

}


for more complex settings you can utilize containers, now you can think of a container as a dictionary of dictionaries, so you can group settings by key. 

private void SaveValue(string Key, Object Value, string ContainerName="Main")
{
    var localSettings = ApplicationData.Current.LocalSettings.CreateContainer(ContainerName, ApplicationDataCreateDisposition.Always);

    if (localSettings.Values.ContainsKey(Key))
        localSettings.Values.Remove(Key);
    localSettings.Values.Add(new KeyValuePair<string, object>(Key, Value));
}

private object GetStoredValue(string Key, string ContainerName = "Main")
{
           
    var localSettings = ApplicationData.Current.LocalSettings;
    if (localSettings.Containers.ContainsKey(ContainerName) && localSettings.Containers[ContainerName].Values.ContainsKey(Key))
        return localSettings.Containers[ContainerName].Values[Key];

    return String.Empty;

}

and that's about it, a lovely thing is that to modify this to use the roaming settings instead is very straight forward, just swap LocalSettings for RoamingSettings. like so

private void SaveValue(string Key, Object Value, string ContainerName = "Main")
{
    var roamingSettings = ApplicationData.Current.RoamingSettings.CreateContainer(ContainerName, ApplicationDataCreateDisposition.Always);

    if (roamingSettings.Values.ContainsKey(Key))
        roamingSettings.Values.Remove(Key);
    roamingSettings.Values.Add(new KeyValuePair<string, object>(Key, Value));
}

private object GetStoredValue(string Key, string ContainerName = "Main")
{
    var roamingSettings = ApplicationData.Current.RoamingSettings;

    if (roamingSettings.Containers.ContainsKey(ContainerName) && roamingSettings.Containers[ContainerName].Values.ContainsKey(Key))
        return roamingSettings.Containers[ContainerName].Values[Key];

    return String.Empty;

}



Thursday 22 January 2015

LocalSettings

There are multiple ways to store user data in winRT apps, one option for simple data is the LocalSettings, now the local settings can be simply used as a key value Collection, but that's rather boring, this is because you could in essence use a Resource dictionary for the same thing.

What's interesting about the LocalSettings date Container is that it can contain sub containers, so for example you could have multiple containers in the LocalSettings container that all have the same sub valuekey pairs, what i mean is you could have something like the following
  • PersonOne
    • FirstName
    • LastName
    • BirthDate
  • PersonTwo
    • FirstName
    • LastName
    • BirthDate
  • PersonThree
    • FirstName
    • LastName
    • BirthDate
  • etc

ok lets look at an example


pretty straight forward app, now let's look at the xaml to make this happen
<Page
    x:Class="pc.LocalSettings.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.LocalSettings"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <Style TargetType="TextBox">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="ComboBox">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="DatePicker">
            <Setter Property="FontSize" Value="34"/>
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="34"/>
            <Setter Property="Margin" Value="0 20 20 0"/>
        </Style>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" Text="LocalSettings Example"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   VerticalAlignment="Center"/>
        <StackPanel Grid.Column="1" Grid.Row="1" Margin="0 0 100 0">
            <ComboBox x:Name="People_CB"/>
            <TextBox x:Name="FirstName_TXT" Header="First Name:"/>
            <TextBox x:Name="LastName_TXT" Header="Last Name:"/>
            <DatePicker x:Name="BirthDate_DP" Header="Date Picker:" />
            <StackPanel Orientation="Horizontal">
                <Button x:Name="Save_BTN" Content="Save"/>
                <Button x:Name="Delete_BTN" Content="Delete"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>


easy enough, i don't think anything warrants an explanation. so let's just jump into the code-behind

using System;
using System.Collections.Generic;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.LocalSettings
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            //Get the local settings
            var localSettings = ApplicationData.Current.LocalSettings;
           
            //use the key of each of the containers as an item
            //in our people combo box
            foreach (var fullName in localSettings.Containers.Keys)
                this.People_CB.Items.Add(fullName);

            //add our event revievers
            this.People_CB.SelectionChanged += People_CB_SelectionChanged;
            this.Save_BTN.Click += Save_BTN_Click;
            this.Delete_BTN.Click += Delete_BTN_Click;
        }

        //delete a contianer from our local settings
        void Delete_BTN_Click(object sender, RoutedEventArgs e)
        {
            //get the key name from the combo box
            var selectedPerson = People_CB.SelectedValue.ToString();

            //get our local settings container
            var localSettings = ApplicationData.Current.LocalSettings;
           
            //delete the container with the specified key
            localSettings.DeleteContainer(selectedPerson);

            //remove the corresponding key from the combo box
            People_CB.Items.Remove(selectedPerson);

            //clear the input values
            this.FirstName_TXT.Text = this.LastName_TXT.Text = String.Empty;
            this.BirthDate_DP.Date = DateTimeOffset.Now;
        }

        //load the data for a specific container
        void People_CB_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //prevent error when deleting a container
            if (e.AddedItems.Count == 0)
                return;

            //get contaier key to load
            var selectedPerson = e.AddedItems[0].ToString();

            //get local settings
            var localSettings = ApplicationData.Current.LocalSettings;

            //load sub container with specified key
            ApplicationDataContainer Person = localSettings.Containers[selectedPerson];

            //populate inputs with specified container values
            this.FirstName_TXT.Text = Person.Values["firstName"].ToString();
            this.LastName_TXT.Text = Person.Values["lastName"].ToString();
            this.BirthDate_DP.Date = (DateTimeOffset)Person.Values["birthDate"];
        }

        void Save_BTN_Click(object sender, RoutedEventArgs e)
        {
            //get values from input fields
            var firstName = FirstName_TXT.Text;
            var lastName = LastName_TXT.Text;
            var birthDate = BirthDate_DP.Date;

            //set key to use
            var fullName = String.Format("{0} {1}", firstName, lastName);

            //get local settings
            var localSettings = ApplicationData.Current.LocalSettings;

            //get or create the specified subcontaier
            ApplicationDataContainer Person =
                localSettings.CreateContainer(firstName, ApplicationDataCreateDisposition.Always);

            //save or update the subcontainers values
            SaveContainerData(Person, "firstName", firstName);
            SaveContainerData(Person, "lastName", lastName);
            SaveContainerData(Person, "birthDate", birthDate);

            if (!this.People_CB.Items.Contains(firstName))
                this.People_CB.Items.Add(firstName);
        }

        void SaveContainerData<T>(ApplicationDataContainer Container, string Key, T Value) {
            //check if contaier already has key, if so update it if not create it.
            if (Container.Values.ContainsKey(Key))
                Container.Values[Key] = Value;
            else
                Container.Values.Add(new KeyValuePair<string, object>(Key, Value));
        }
    }
}


the code-behind is pretty straight forward as well, the only thing really worth mentioning, is when creating a sub container that is

ApplicationDataContainer Person = localSettings.CreateContainer(firstName, ApplicationDataCreateDisposition.Always);

you have two options
Always:loads the container if it exists or creates it if it doesn't
Existing:only loads contains that exist throws and exception if you try to create a new one.