X

Generalizing storage access for Windows Phone and WinRT apps

When building application that works both on WinRT and Window Phone you use Portable Class Libraries (PCL) for shared classes. As there are many application specific things that are not same on different platforms or that are not supported by PCL then you have to make some architectural decisions when creating shared functionalities. In this posting I will focus on persisting data for offline use.

Problem: Different storage implementations

As WinRT and Windows Phone application both use same data storing logic it is implemented in PCL to avoid duplicating the code. The class that needs to load and save data is defined in PCL but there is no generic storage interface or class defined in PCL that WinRT and Windows Phone applications can use.

The problem is: how to make storage operations on different platforms available to shared library that is used by applications running on different platforms and having different storage logic.

Breaking dependency to storage logic

Usually we have already one application written to some point when starting with another. When starting with another application we move shared functionality to PCL and find out that most of our original platform classes are not available there.

public interface IStorageProvider
{
    byte[] GetData();
    void SaveData(byte[] data);       
}

Next we make our DataManager class to use this interface to communicate with storage.

public class DataManager
{
    private readonly IStorageProvider _provider;

    public DataManager(IStorageProvider provider)
    {
        _provider = provider;
    }

    // ...
}

When DataManager class is created we give it some class that implements IStorageProvider interface and DataManager is happy.

Example: Windows Phone storage provider

Windows Phone uses isolated storage for application settings and files. Isolated storage is supported only in Windows Phone and therefore it is not supported by PCL. We have to define storage provider for Windows Phone in our Windows Phone application.

public class WindowsPhoneStorageProvider : IStorageProvider
{
    public byte[] LoadData()
    {
        using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (!storage.FileExists("data.dat"))
                return null;

            using (var file = storage.OpenFile("data.dat", FileMode.Open, FileAccess.Read, FileShare.None))
            {
                var data = new byte[file.Length];
                file.Read(data, 0, data.Length);
                return data;
            }
        }
    }

    public void SaveData(byte[] data)
    {
        using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (storage.FileExists("data.dat"))
                storage.DeleteFile("data.dat");

            using (var file = storage.OpenFile("data.dat", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                file.Write(data, 0, data.Length);
                file.Flush(true);
            }
        }
    }
}

Example: WinRT storage provider

On WinRT we must write different code to use application storage. Here I’m using local storage for my data file.

public class WinRTStorageProvider : IStorageProvider
{
    public string UserName
    {
        get { return ""; }
    }

    public async void SaveData(byte[] data)
    {
        var storageFolder = ApplicationData.Current.LocalFolder;
        var file = await storageFolder.CreateFileAsync("data.dat", CreationCollisionOption.ReplaceExisting);
        using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
        using (var writer = new DataWriter(stream))
        {
            writer.WriteBytes(data);
            await writer.StoreAsync();
        }
    }

    public async Task<byte[]> ReadData()
    {
        var storageFolder = ApplicationData.Current.LocalFolder;
        var file = await storageFolder.GetFileAsync("data.dat");
        using (var stream = await file.OpenAsync(FileAccessMode.Read))
        using (var inputStream = stream.GetInputStreamAt(0))
        using (var reader = new DataReader(inputStream))
        {
            var data = new byte[stream.Size];
            await reader.LoadAsync((uint)data.Length);
            reader.ReadBytes(data);
            return data;
        }
    }
}

Wrapping up

Now we can use our DataManager in PCL library with different storage providers. DataManager knows storage providers by IStorageProvider interface, keeping itself away from storage implementations. Storage implementations are defined in specific applications or their libraries and these implementations are given to DataManager when data manager class is created. We can easily change internal storage logic if we need and we don’t have to modify code in another libraries.

Liked this post? Empower your friends by sharing it!

View Comments (6)

  • IStorageProvider has GetData() method with return type of byte[] but WinRTStorageProvider has GetData() method with return type of Task, so do I need to add more methods in interface and do I need to implement those unnecessary methods in both class?

  • You can easily turn serialized data as string to bytes and back. Check System.Encoding.UTF8Encoding.UTF8 methods.

  • @Farhan In the WindowsPhoneStorageProvider wrap the LoadData in a
    await Task.Run(() => { /* code here */}); and return a Task that should work. And don't forget to change the Interface.

Related Post