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.
Pingback:Generalizing storage access for Windows Phone and WinRT apps | TechBlog
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?
Can you make this with serialized data? It would be very helpful.
You can easily turn serialized data as string to bytes and back. Check System.Encoding.UTF8Encoding.UTF8 methods.
Farhan, with async methods you just return Task. There’s nothing wrong as compiler handles the situation.
But I am getting error that you didn’t implement all the methods of interface.
@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.