Windows/Phone 8.1–Returning to the File Open Picker Abstraction

In this post about dealing with the differences across Windows/Phone 8.1, I finished up with some pieces about the differences between the FileOpenPicker (and the FileSavePicker) on Windows Phone 8.1 versus Windows 8.1.

There are other APIs like this in the Windows RT framework – collectively they are known as the “AndContinue” methods and this blog post by Bret talks more about them;

“Explaining the AndContinue methods for Windows Phone 8.1 SDK”

As the post says – there are APIs in WinRT which are async and where the expectation is that there is enough memory on the device in order to keep the app running while the API performs its work and then gathers some result and returns it back to the calling application (think of the FileOpenPicker example).

On the Windows Phone, the memory required to perform the operation (like file picking) might not be available on the device and so these “AndContinue” APIs all cause the application to be suspended while the API does its work and it’s possible that the app will be terminated by the OS in order for that work to be completed. This is something that I don’t think ever happens on Windows 8.1 – i.e. you call an API which may cause the OS to terminate your app. Generally, on Windows 8.1 the OS terminates your app only after the user has switched away from it and it has already been suspended (after around 8 seconds).

Again, there’s an MSDN article here around;

“How to continue your Windows Phone Store app after calling an AndContinue method”

In that previous blog post, I had some code that tried to abstract around these differences by using shared compilation whereas I recently showed the same sort of thing in a couple of conference sessions where I did things differently and so I thought I would share that here as a follow up.

I built two very, very simple Windows/Phone apps which just show the FileOpenPicker and to pick an image file which is then displayed on the screen. Here’s the Windows version in “action”;

image

image

image

and here’s the Windows Phone version in action;

123

As described, the difference between the 2 applications is that on clicking the “Select File” button in Windows Phone, the app will be immediately suspended and may be terminated and in that case it would need re-running when the FileOpenPicker returns a file – hence the AndContinue naming on the API.

In order to abstract the difference between Windows/Phone I think any abstraction interface has to deal with 3 scenarios;

  1. The FileOpenPicker is invoked with PickSingleFileAsync on Windows 8.1 and returns (asynchronously) a StorageFile.
  2. The FileOpenPicker is invoked with PickSingleFileAndContinue on Windows Phone 8.1 and the app is suspended but then later resumed and the StorageFile is returned to the same app process via Activated event on the app.
  3. The FileOpenPicker is invoked with PickSingleFileAndContinue on Windows Phone 8.1 and the app is suspended and then terminated and the app then has to be run from “cold” and Activated in order to receive the StorageFile that the picker has obtained.

In order to achieve this in an abstracted way, I can’t think of avoiding a requirement to have the abstraction Initialise itself in some way when the application starts up such that it can be in the right “place” to handle the Activated event should it need to, hence I ended up with an interface that looks like this;

  using System.Threading.Tasks;
  using Windows.Storage;
  using Windows.Storage.Pickers;

  public interface IFileSelectionService
  {
    void Initialise();
    Task<StorageFile> DisplayPickerAsync(FileOpenPicker picker);
    StorageFile GetAndClearLastPickedFile();
  }

I built that into a portable class library along with some other pieces and for my simple demo I paid no real consideration to the suspend/terminate state management that a real app would need to do but built on 6 different projects as below;

image

Here there’s a Windows app, a Phone app, a Portable library, a Windows library, a Windows Phone library and a shared folder.

The Windows library contains a Windows specific implementation of the IFileSelectionService interface shown above and the Windows Phone library contains a Windows Phone specific implementation. I have nothing in the Windows/Phone application projects themselves other than the standard bitmaps and application manifests as you can see below – everything else I use here is either in libraries or the shared folder.

image

The Shared Folder

The Shared folder contains both my App.xaml(.cs) and my MainPage.xaml(.cs) and my App.xaml has a simple ViewModelLocator instance defined;

<Application x:Class="FileSelection.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="using:FileSelection"
             xmlns:vm="using:PortableLibrary.Common">

  <Application.Resources>
  
    <vm:ViewModelLocator x:Key="locator" />

  </Application.Resources>

</Application>

the code that lives with that XAML has some “tricks” to initialise that ViewModelLocator such that it knows when the application starts up which implementation of the IFileSelectionService is to be used. There’s two things going on in the code below. Firstly, the creation of the UI has been moved into a function called CreateUserInterface() which is called both from OnLaunched and OnActivated. Secondly, the constructor makes a call to InitialiseFileSelectionService which makes a call to a static method on a ViewModelLocator class and there’s conditional compilation used to either initialise with a WindowsFileSelectionService or a PhoneFileSelectionService.

namespace FileSelection
{
  using FileSelectionService;
  using PortableLibrary.Common;
  using System;
  using Windows.ApplicationModel.Activation;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  public sealed partial class App : Application
  {
    void InitialiseFileSelectionService()
    {
#if WINDOWS_APP      
      ViewModelLocator.Initialise<WindowsFileSelectionService>();
#else
      ViewModelLocator.Initialise<PhoneFileSelectionService>();
#endif

    }

    public App()
    {
      this.InitializeComponent();
      this.InitialiseFileSelectionService();
    }

#if WINDOWS_PHONE_APP
    private TransitionCollection transitions;
#endif

    protected override void OnLaunched(LaunchActivatedEventArgs e)
    {
      this.CreateUserInterface();
    }

    protected override void OnActivated(IActivatedEventArgs args)
    {
      this.CreateUserInterface();
    }

    void CreateUserInterface()
    {
      Frame rootFrame = Window.Current.Content as Frame;

      // Do not repeat app initialization when the Window already has content,
      // just ensure that the window is active
      if (rootFrame == null)
      {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        // TODO: change this value to a cache size that is appropriate for your application
        rootFrame.CacheSize = 1;

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
      }

      if (rootFrame.Content == null)
      {
#if WINDOWS_PHONE_APP
        // Removes the turnstile navigation for startup.
        if (rootFrame.ContentTransitions != null)
        {
          this.transitions = new TransitionCollection();
          foreach (var c in rootFrame.ContentTransitions)
          {
            this.transitions.Add(c);
          }
        }

        rootFrame.ContentTransitions = null;
        rootFrame.Navigated += this.RootFrame_FirstNavigated;
#endif

        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
        if (!rootFrame.Navigate(typeof(MainPage), null))
        {
          throw new Exception("Failed to create initial page");
        }
      }

      // Ensure the current window is active
      Window.Current.Activate();
    }

#if WINDOWS_PHONE_APP
    private void RootFrame_FirstNavigated(object sender, NavigationEventArgs e)
    {
      var rootFrame = sender as Frame;
      rootFrame.ContentTransitions = this.transitions ?? new TransitionCollection() { new NavigationThemeTransition() };
      rootFrame.Navigated -= this.RootFrame_FirstNavigated;
    }
#endif

  }
}

There’s then a MainPage.xaml(.cs) which has some XAML to display the “UI” and that XAML is setting the DataContext to a property on the ViewModelLocator called ViewModel and there’s then bindings to two properties called UserImage and SelectImageCommand;

<Page x:Class="FileSelection.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:FileSelection"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
      DataContext="{Binding Source={StaticResource locator}, Path=ViewModel}">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Image Source="{Binding UserImage}"
           Stretch="Uniform" />
    <Button Grid.Row="1"
            x:Uid="btnSelectImage"
            Command="{Binding SelectImageCommand}" />
  </Grid>
</Page>

the code-behind this XAML file uses a cheap/cheerful trick that I use from time to time when I need an easy way of calling some initialisation code on a DataContext;

namespace FileSelection
{
  using PortableLibrary.Interface;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Navigation;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      IDataContextInitialise initialisableContext =
        this.DataContext as IDataContextInitialise;

      if ((initialisableContext != null) && (e.NavigationMode == NavigationMode.New))
      {
        initialisableContext.Initialise();
      }
    }
  }
}

and so as this page is NavigatedTo it will take a look at its DataContext (which never changes in my case) and if that context supports IDataContextInitialise it will call an Initialise() method on it. This becomes important at a later stage in the app because it’s the way in which the view model will attempt to retrieve a file in the circumstance where the app has been terminated and restarted.

The Portable Library

My portable class library contains that simple interface IDataContextInitialise;

namespace PortableLibrary.Interface
{
  public interface IDataContextInitialise
  {
    void Initialise();
  }
}

and it contains the ViewModelLocator class that I used in App.xaml;

namespace PortableLibrary.Common
{
  using Autofac;
  using PortableLibrary.Interface;
  using PortableLibrary.ViewModel;

  public class ViewModelLocator
  {
    public object ViewModel
    {
      get
      {
        return (container.Resolve<MainViewModel>());
      }
    }
    public static void Initialise<T>() where T : IFileSelectionService, new()
    {
      ContainerBuilder builder = new ContainerBuilder();

      T t = new T();

      t.Initialise();

      builder.RegisterInstance<IFileSelectionService>(t);
      
      builder.RegisterType<MainViewModel>().AsSelf();

      container = builder.Build();
    }
    static IContainer container;
  }
}

This is pretty simple – it has that one property called ViewModel (referenced from MainPage.xaml) which uses an Autofac IoC container to resolve the MainViewModel. The Initialise<T> function which we’ve already seen called in App.xaml.cs creates the container by trying to instantiate the IFileSelectionService type it is given, calling Initialise() on that service (which is also going to be important) and then registering a single initialised instance in the container.

The MainViewModel class is also in the portable class library and, as you might expect, it has a dependency on the IFileSelectionService which the container is going to resolve for it. It’s as below;

namespace PortableLibrary.ViewModel
{
  using PortableLibrary.Common;
  using PortableLibrary.Interface;
  using System;
  using System.Threading.Tasks;
  using System.Windows.Input;
  using Windows.Storage;
  using Windows.Storage.Pickers;
  using Windows.UI.Xaml.Media;
  using Windows.UI.Xaml.Media.Imaging;

  class MainViewModel : ViewModelBase
  {
    public MainViewModel(IFileSelectionService fileSelectionService)
    {
      this.fileSelectionService = fileSelectionService;
      this.SelectImageCommand = new DelegateCommand(OnSelectImageCommand);
    }
    public override async void Initialise()
    {
      StorageFile file = 
        this.fileSelectionService.GetAndClearLastPickedFile();

      if (file != null)
      {
        this.UserImage = await MakeImage(file);
      }
    }
    async Task<ImageSource> MakeImage(StorageFile file)
    {
      BitmapImage bitmapImage = null;
      
      if (file != null)
      {
        bitmapImage = new BitmapImage();

        using (var stream = await file.OpenReadAsync())
        {
          await bitmapImage.SetSourceAsync(stream);
        }
      }
      return (bitmapImage);
    }
    public ImageSource UserImage
    {
      get
      {
        return (this.userImage);
      }
      private set
      {
        base.SetProperty(ref this.userImage, value);
      }
    }
    public ICommand SelectImageCommand
    {
      get
      {
        return (this.selectImageCommand);
      }
      private set
      {
        base.SetProperty(ref this.selectImageCommand, value);
      }
    }
    async void OnSelectImageCommand()
    {
      FileOpenPicker picker = new FileOpenPicker();
      picker.FileTypeFilter.Add(".jpg");
      picker.FileTypeFilter.Add(".png");

      StorageFile file = await this.fileSelectionService.DisplayPickerAsync(picker);

      // this code may never run on Windows Phone 8.1 because we could get
      // suspended and terminated as part of the regular file selection
      // process.
      if (file != null)
      {
        this.UserImage = await this.MakeImage(file);
      }
    }
    IFileSelectionService fileSelectionService;
    ICommand selectImageCommand;
    ImageSource userImage;
  }
}

and so the crucial thing here is that this class has been built around the IFileSelectionService abstraction interface. In the OnSelectImageCommand() function, it asks the file selection service to display a FileOpenPicker and it attempts to await that call for the StorageFile result. Sometimes this will happen. Othertimes, on Windows Phone 8.1, this code may never run because the process is terminated and re-started.

That scenario is why this ViewModel has an Initialise() function. That function calls IFileSelectionService.GetAndClearLastPickedFile() to ask the file selection service whether (at app startup) it has gathered a StorageFile handed to it when the app has been restarted by the OS and so can deliver it as soon as the MainViewModel is created. This will never happen on Windows 8.1 but may happen on Windows Phone 8.1.

So, the MainViewModel is meant to be “resilient” in that it can cope with the 2 possible approaches. The only thing that’s left is to attempt to implement those 2 possible approaches behind that IFileSelectionService interface.

The WindowsLibrary

In my Windows library project (only referenced by my Windows app project) I have the implementation of IFileSelectionService for Windows 8.1. In this simple demo, this is simple code;

namespace FileSelectionService
{
  using PortableLibrary.Interface;
  using System;
  using System.Threading.Tasks;
  using Windows.Storage;
  using Windows.Storage.Pickers;

  public class WindowsFileSelectionService : IFileSelectionService
  {
    public void Initialise()
    {
    }
    public async Task<StorageFile> DisplayPickerAsync(FileOpenPicker picker)
    {
      StorageFile file = await picker.PickSingleFileAsync();
      return (file);
    }
    public StorageFile GetAndClearLastPickedFile()
    {
      return (null);
    }
  }
}

and so this code is simply delegating down to the ability already present on the FileOpenPicker via PickSingleFileAsync.

The PhoneLibrary

In my Phone library project (only referenced by my Phone app project) I have the implementation of IFileSelectionService for Windows Phone 8.1. In this simple demo code I tried to implementation that abstraction;

namespace FileSelectionService
{
  using PortableLibrary.Interface;
  using System.Threading.Tasks;
  using Windows.ApplicationModel.Activation;
  using Windows.ApplicationModel.Core;
  using Windows.Storage;
  using Windows.Storage.Pickers;

  public class PhoneFileSelectionService : IFileSelectionService
  {
    public void Initialise()
    {
      // Feels a bit wrong to call into CoreApplication here without an
      // abstraction but for demo purposes...

      // This event can fire either if the app keeps running after a
      // file selection or if the app is killed/re-launched after a
      // file selection.
      CoreApplication.GetCurrentView().Activated += OnApplicationActivated;
    }
    void OnApplicationActivated(CoreApplicationView sender, 
      IActivatedEventArgs args)
    {
      FileOpenPickerContinuationEventArgs continueArgs =
        args as FileOpenPickerContinuationEventArgs;

      if (continueArgs != null)
      {
        // assumes we have one file here at least.
        this.selectedFile = continueArgs.Files[0];

        if (this.completionSource != null)
        {
          this.completionSource.SetResult(this.selectedFile);

          this.completionSource = null;
        }
      }
    }
    public async Task<StorageFile> DisplayPickerAsync(FileOpenPicker picker)
    {
      this.completionSource = new TaskCompletionSource<StorageFile>();

      picker.PickSingleFileAndContinue();

      StorageFile file = await this.completionSource.Task;

      return (file);
    }
    public StorageFile GetAndClearLastPickedFile()
    {
      StorageFile file = this.selectedFile;

      this.selectedFile = null;

      return (file);
    }
    TaskCompletionSource<StorageFile> completionSource;
    StorageFile selectedFile;
  }
}

Firstly, the Initialise() method is important because it handles the CoreApplicationView.Activated event to look for activation events which are a continuation of a FileOpenPicker operation. This can happen 2 ways;

  1. The app stayed running after a call to FileOpenPicker.PickSingleFileAndContinue and this event was fired.
  2. The app was terminated after that call, was re-activated and this event was fired.

The DisplayPickerAsync method tries to use a TaskCompletionSource to put an async facade over the top of scenario (1) above and for scenario (2) as long as the Initialise() method is called early enough in the app’s lifecycle the Activated event will be handled, the StorageFile will be retrieved and stored such that a caller can pick it up via the GetAndClearLastPickedFile() method.

Summing Up

This is just demo code, lacking exception handling and so on and possibly there’s a few bugs lurking around in there too but I thought I’d share this “tidied” version of what I talked about in that previous blog post.

The code’s here for download if you want to look at it in it’s entirety rather than in the pieces above. If you want to debug it then the process to follow for Phone is;

  1. Use the debugger in “Don’t Launch” mode as I described here.
  2. Run the phone app.
  3. Hit the button to invoke the FileOpenPicker.
  4. Use the debugger’s menu to “Suspend and Shutdown” the app.

image

  1. Select the file in the FileOpenPicker and see the application get re-activated and accept the StorageFile.

and you can play around with some breakpoints to see how that file arrives and gets displayed in the page.