1. Storing Data
Apps need data. Data needs to be persistent.
That’s the big picture. You need to be able to store data on a phone for
use by your application. That data needs to remain on the phone between
invocations of your application as well as through reboots. Although
Windows Phone supports this in several ways, you will need to understand
the various facilities to best decide how to accomplish this.
The two main ways to store data are through storage and a local database. Storage
enables you to store data as files in a virtualized file system that
only your application has access to as well as read data from your
installation directory and on external storage (for example, SD cards).
In addition to storage, you can also store information in a local database that provides
you with a way to store data and query the data for retrieval. From
those broad strokes, it might seem that storing data in a database is
the way to go. This is often the most common approach for developers
transitioning to the phone. The problem is that you need to determine
whether you need a database.
Using the phone’s database requires more
resources than simply storing files. It might seem easier, but that
simplicity comes at a cost. The reality is that many applications do not
need the query and update facilities of a full-blown database system.
Often what they really need is to simply store data for the next time
the application is started.
2. Storage
As stated earlier, one of the options is to
use the file system on the phone. Each application has access to three
types of storage: local folder, application folder, and external
storage. You can write only to the local folder, but you can read from
both your app’s installation directory and any external storage such as
SD cards.
The local folder is allocated a separate private
part of the internal file system for its use. This means any files you
store in the local folder is for your application’s use only. It is not
accessible by any other application (although the operating system has
access to these files). When your application is installed, this area of
the file system is allocated. Likewise, when your application is
uninstalled, any files that are in the local folder are lost.
Accessing the local folder on the phone can be
accomplished by using the isolated storage classes from .NET or using
the Storage API from WinRT. Although System.IO
and the
related APIs work and are useful if you are sharing code with .NET
libraries, the preference is to use the WinRT storage classes because
they are friendlier to the asynchronous access supported on the phone.
To work with the data in the local folder, you start with the ApplicationData
class in the Windows.Storage
namespace. This class enables you to get the Current
starting point for your application data. This lets you access the LocalFolder
object that represents the place to store data for your application. The ApplicationData
class includes the properties and methods for the other endpoints that
are supported on Windows 8, but they are not implemented on the phone
(for example, LocalSettings
or RoamingFolder
):
var folder = ApplicationData.Current.LocalFolder;
From this folder object, you can create a new file using the CreateFileAsync
method. As you might expect, the Async
suffix indicates that this is an asynchronous method. Because the Windows Phone is based on .NET 4.5, it supports the async
and await
keywords. By specifying the async
keyword on the method that contains this code, you can use the await
keyword to let you write the asynchronous code easier.
async void saveButton_Click(object sender, RoutedEventArgs e)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync("foo.txt",
CreationCollisionOption.ReplaceExisting);
}
The CreateCollisionOption
enumeration supports several options when the file creation finds an
existing file. This includes replacing the file, open an existing file,
throwing an error, or even generating a unique name. After the file is
created, the file object is returned.
Typically, the WinRT APIs use different types of objects to work with the data inside a file, but using the System.IO
classes can make your code easy to use. Therefore, you should work directly with the Stream
class. The System.IO
namespace has extension methods on the Windows.Storage
classes to retrieve actual Stream
objects. For example, on the OpenStreamForWriteAsync
is an extension method on the StorageFile
class to get a Stream
object so you can use the standard System.IO
classes:
using System.IO;
...
async void saveButton_Click(object sender, RoutedEventArgs e)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync("foo.txt",
CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenStreamForWriteAsync())
{
var writer = new StreamWriter(stream);
await writer.WriteLineAsync("color=blue;");
await writer.FlushAsync();
writer.Close();
}
}
Notice the multiple uses of the await
keyword. Each of these operations is asynchronous, but with the async
and await
keywords, you can write the code as if it were synchronous without
worrying about locking up the user interface thread (and leaving your
app hanging during this operation).
Opening a file is similar in that you would use the OpenStreamForReadAsync
to retrieve a stream of the file. Again, this is returning a stream, and you can use the System.IO
classes to do the work of reading the file for you:
async void loadButton_Click(object sender, RoutedEventArgs e)
{
var folder = ApplicationData.Current.LocalFolder;
using (var stream = await folder.OpenStreamForReadAsync("foo.txt"))
{
var reader = new StreamReader(stream);
var result = await reader.ReadToEndAsync();
MessageBox.Show(result);
}
}
Although you can get the folder and then read a
file, you can actually skip the folder step by using a URI with a
moniker instead:
var fileUri = new Uri("ms-appdata:///local/foo.txt");
var file = await StorageFile.GetFileFromApplicationUriAsync(fileUri);
using (var stream = await file.OpenStreamForReadAsync())
{
var reader = new StreamReader(stream);
var result = await reader.ReadToEndAsync();
MessageBox.Show(result);
}
In this example, you can just use a full URI to specify that you want to get from the local folder. The ms-appdata:///local/
start of the URI tells the storage API to find the file in the local
folder. You can also load a file from the installation directory using
the ms-appx:///
var fileUri = new Uri("ms-appx:///someconfig.xml");
var file = await StorageFile.GetFileFromApplicationUriAsync(fileUri);
using (var stream = await file.OpenStreamForReadAsync())
{
var reader = new StreamReader(stream);
var result = await reader.ReadToEndAsync();
MessageBox.Show(result);
}
The ms-appx:///
moniker tells the
storage API to look in the directory of the installation of your
application. Specifically, this means that if you mark any file as
“Content” in your project, it will be packaged with the code and
delivered to the installation directory. This is an easy way to access
files delivered with your application. The installation directly can be
accessed only for reading. Writing to the installation directory is not
allowed; if you need to write new files, you should always use the local
folder instead.
Before you can access this functionality, you have to specify several things. First, your application must specify the ID_CAP_REMOVABLE_STORAGE
capability in the WMAppManifest.xml file.
To access files on the memory card, you’ll need to retrieve all the external storage files. You can do this by calling the ExternalStorage
’s GetExternalStorageDevicesAsync
method, as shown here:
async void sdButton_Click(object sender, RoutedEventArgs e)
{
var devices = await
ExternalStorage.GetExternalStorageDevicesAsync();
var sdCard = devices.FirstOrDefault();
if (sdCard != null)
{
// ...
}
}
In this case you can just get the first device
found (there is only one support on the Windows Phone). If the device is
null, no card is installed.
After you have a valid card, you can simply open a file based on the path using GetFileAsync
:
var file = await sdCard.GetFileAsync("some.foo");
using (var stream = await file.OpenForReadAsync())
{
var reader = new StreamReader(stream);
var result = await reader.ReadToEndAsync();
MessageBox.Show(result);
}
Calling the file object’s OpenForReadAsync
because it returns a Stream
object that can be manipulated directly using the System.IO
classes in .NET. This API does support iterating through supported
files and directories as well. For instance, to iterate through all the
files in the root folder of the memory card, you can do the following:
foreach (var file in await sdCard.RootFolder.GetFilesAsync())
{
MessageBox.Show(file.Name);
}