Wednesday, May 29, 2013

Sharing App Content using MVVM and the Okra App Framework

One of the key differentiating features of the Windows Store application model in Windows 8 is the availability of system provided “charms” for common tasks such as sharing, search and application settings. In this post I will focus on sharing, and how the Okra App Framework makes it easy to incorporate this functionality into applications using the MVVM pattern.

How to share content the Windows 8 way?

The MSDN documentation details how to share content from Windows Store apps in C#/VB. The first step is to get a reference to the application’s DataTransferManager, and then register for its DataRequested event. When the user selects the share charm this event is fired, passing a DataRequestedEventArgs object to the event handler.

There are a number of problems with this approach in the context of an MVVM application,

  • The content to share should be determined dynamically based upon the page displayed and any user selected content. In the MVVM pattern this is best accomplished by the view-model, and not by a single event handler.
  • The application specific sharing code is passed a DataRequestedEventArgs object (which is a sealed class with no public constructors). This makes unit testing this code difficult.
  • Supporting pull operations (see the MSDN documentation on this topic) requires additional event handler registration, not following the typical Task based async support used elsewhere.

Sharing content with the Okra App Framework

The Okra App Framework overcomes these problems, whilst maintaining the power of the sharing contract by allowing view-models to implement a simple, easily unit-testable, IShareable interface. The framework will handle all the other infrastructure required for application sharing. Since the IShareable interface is applied to the view-model, a ‘ViewPhoto’ page can share an individual photo, whilst a ‘ViewAlbum’ page can share the whole album (or selected photos if the user has done so).

The first step in adding sharing support to an application is to add a reference to an IShareSourceManager in the application bootstrapper.

public class AppBootstrapper : OkraBootstrapper
{
    [Import]
    public IShareSourceManager ShareSourceManager { get; set; }
}

You can then add the IShareable interface to any view-model that has suitable data to share,

[ViewModelExport("MyPage")]
public class MyViewModel : IShareable
{
    public void ShareRequested(IDataRequest dataRequest)
    {
        ...
    }
}

You will notice that the ShareRequested method is passed an IDataRequest object in which to return the data to share. This is a unit-testable wrapper around the WinRT DataRequest object and the standard MSDN documentation can be followed from this point to respond to the share request. For example to share some text you would implement this as follows,

[ViewModelExport("MyPage")]
public class MyViewModel : IShareable
{
    public void ShareRequested(IDataRequest dataRequest)
    {
        dataRequest.Data.Properties.Title = "...";
        dataRequest.Data.Properties.Description = "...";
 
        request.Data.SetText("Hello World!");
    }
}

Sharing content asynchronously (optional)

There are occasions when you may wish to only download the data once the user has completed the share request (e.g. when sharing a large file from a web service) – these are known as pull operations. The Okra App Framework provides extension methods to allow you to implement pull operations using the async features of .Net. For each SetXXX(…) method, Okra provides a corresponding SetAsyncXXX(…) method. For example, the code below demonstrates how to return text returned from some async method call. Note the signature of the GetShareTextAsync(…) method.

[ViewModelExport("MyPage")]
public class MyViewModel : IShareable
{
    public void ShareRequested(IDataRequest dataRequest)
    {
        ...
 
        request.Data.SetAsyncText(GetShareTextAsync);
    }
 
    private async Task<string> GetShareTextAsync(string formatId, DateTimeOffset deadline)
    {
        string text = await SomeAsyncMethod();
        return text;
    }
}

No comments: