This post is really some “rough and ready” notes so feel free to apply a pinch of salt – it came out of chatting to my colleague Martin about C#/XAML/.NET universal projects in Visual Studio 2013 Update 2 RC for Windows/Phone 8.1 applications and how Windows/Phone 8.1 applications have pretty much the same APIs available to them with a small amount of platform-specific exceptions.
That discussion turned to ways in which a developer can “manage” that platform specific code within a ‘universal’ project in Visual Studio, what that code might look like and so I thought I’d write up a few notes here around some of the things that we talked about.
To illustrate – I’ll start off with a “simple” scenario – let’s say that I have a page (view) with a button on it that is bound to a command on a view model and the invocation of that command requires some platform specific functionality to be used.
As the API sets between Windows/Phone are so common, it’s quite hard to come up with what that platform specific functionality might be but I settled on the idea of sending a “quiet” toast notification as that’s only really an option on Windows Phone 8.1 right now where the “quiet” notification gets sent into the Action Center without being first presented to the user.
I made a quick, blank, ‘universal’ app project and I added my view, view model and a service all into the “Shared” folder as they are going to be the same on each platform and I added a quick implementation of ICommand as there isn’t one in the XAML framework anywhere.
Here’s the project “structure”;
and essentially, my view just displays a button and data-binds its command;
<Page x:Class="App115.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App115" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.DataContext> <local:MainPageViewModel /> </Page.DataContext> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button Content="Toast" Command="{Binding InvokeCommand}" /> </Grid> </Page>
and that ViewModel is wired up by the XAML (rather than use some kind of framework/pattern/IoC container);
using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; namespace App115 { class MainPageViewModel { public MainPageViewModel() : this(null) { } // Parameter should really be an injected dependent service but I'll make this // cheap and cheerful for now to avoid having to add all that complexity here. public MainPageViewModel(INotificationService notificationService) { this.notificationService = notificationService; if (this.notificationService == null) { // again, avoiding adding an IoC container and so on. this.notificationService = new NotificationService(); } this.InvokeCommand = new DelegateCommand(OnInvokeCommand); } void OnInvokeCommand() { this.notificationService.ToastNotifyQuietlyIfPossible("Hello!"); } INotificationService notificationService; public ICommand InvokeCommand { get; private set; } } }
and then the service currently has an interface that looks like this;
namespace App115 { interface INotificationService { void ToastNotifyQuietlyIfPossible(string notification); } }
and the concrete implementation of that is (for brevity here) simply created by the view model if it’s not provided with an implementation at construction time (which it won’t be because it’s created from XAML above).
That service is the first piece of code that I’ve got which is “platform specific” because I want it to show a toast notification “quietly” which it can do on Windows Phone 8.1 but not on Windows 8.1. Hence, even though it’s a very, very small piece of platform specific code I can deal with it in different ways. Here’s some examples.
Conditional Compilation
I can put the service implementation into the “shared” folder of my project and I can conditionally compile the pieces that are Windows/Phone specific. Given that in this case I have just one line of code that differs between the 2 platforms that might not be a bad choice;
namespace App115 { using NotificationsExtensions.ToastContent; using Windows.Data.Xml.Dom; using Windows.UI.Notifications; class NotificationService : INotificationService { public void ToastNotifyQuietlyIfPossible(string text) { ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier(); IToastText01 toast = ToastContentFactory.CreateToastText01(); toast.TextBodyWrap.Text = text; XmlDocument xml = new XmlDocument(); xml.LoadXml(toast.GetContent()); ToastNotification notification = new ToastNotification(xml); #if WINDOWS_PHONE_APP notification.SuppressPopup = true; #endif notifier.Show(notification); } } }
The pros of this are that it’s pretty simple (especially with one line of conditional code). The cons might be that I can’t build this code once into (e.g.) one library in order to re-use it at the binary level and also that if I needed to add more conditional code then that starts to have an impact on how maintainable the code is.
Partial Classes/Methods
One way to remove the conditional compilation above is to have the class definition split across both the Shared folder and provide “more” or “different” implementations of particular parts of the class on a per-platform basis.
For example, I could refine that class definition above to look something like this;
namespace App115 { using NotificationsExtensions.ToastContent; using Windows.Data.Xml.Dom; using Windows.UI.Notifications; partial class NotificationService : INotificationService { partial void AddPlatformSpecificToastRequirements(ToastNotification notification); public void ToastNotifyQuietlyIfPossible(string text) { ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier(); IToastText01 toast = ToastContentFactory.CreateToastText01(); toast.TextBodyWrap.Text = text; XmlDocument xml = new XmlDocument(); xml.LoadXml(toast.GetContent()); ToastNotification notification = new ToastNotification(xml); // Look ma, no conditional compilation. this.AddPlatformSpecificToastRequirements(notification); notifier.Show(notification); } } }
This compiles and works on both platforms as it stands but, of course, I’ve lost the “quiet” nature of the Toast notification on Windows Phone 8.1. I can easily fix this by adding an additional piece of this class definition to the Windows Phone 8.1 project alone;
and then because this code will be effectively compiled with the code from the shared folder it’s possible for me to write some more of the partial class here and I particularly like how I can just type the word partial into Visual Studio in order to get it to complete the rest of the method signature for me;
namespace App115 { using Windows.UI.Notifications; partial class NotificationService : INotificationService { partial void AddPlatformSpecificToastRequirements( ToastNotification notification) { notification.SuppressPopup = true; } } }
Whether this is better/worse than the conditional compilation approach possibly depends on how much conditional compilation you’re doing. I think for the “one line” case that I’ve got here it’s perhaps overkill to split this out into a separate file in a separate project because it’s maybe not so discoverable and someone coming along to maintain the code might puzzle over why there’s an implementation of this method only in the Windows Phone 8.1 project.
Again, I can’t split partial class definitions across binary units (assemblies) and so I couldn’t build part of the class into one library and then use it in many places from various projects so, in that sense, this is similar to the conditional compilation approach.
Another approach to doing this might be to replace the compile-time addition of this “AddPlatformSpecificToastRequirements” method with a runtime replacement via inheritance. If I back out the changes that I made here and continue on…
Inheritance
I could alter the class that’s resident in my shared folder that’s providing the notification service such that it has an additional virtual method;
namespace App115 { using NotificationsExtensions.ToastContent; using Windows.Data.Xml.Dom; using Windows.UI.Notifications; class NotificationService : INotificationService { protected virtual void AddPlatformSpecificToastRequirements( ToastNotification notification) { // base class implementation doesn't do anything } public void ToastNotifyQuietlyIfPossible(string text) { ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier(); IToastText01 toast = ToastContentFactory.CreateToastText01(); toast.TextBodyWrap.Text = text; XmlDocument xml = new XmlDocument(); xml.LoadXml(toast.GetContent()); ToastNotification notification = new ToastNotification(xml); // Look ma, no conditional compilation. this.AddPlatformSpecificToastRequirements(notification); notifier.Show(notification); } } }
If I now want the Windows Phone version of the app to still add its additional behaviour then I can add a new class to the Windows Phone project;
namespace App115 { using Windows.UI.Notifications; class WindowsPhoneNotificationService : NotificationService { protected override void AddPlatformSpecificToastRequirements( ToastNotification notification) { base.AddPlatformSpecificToastRequirements(notification); notification.SuppressPopup = true; } } }
and that’s all “kind of nice” but it comes with the cost that the NotificationService class is now different on Windows Phone 8.1 to Windows 8.1 and, at present, I’ve done a bit of a nasty thing in that my view model explicitly knows how to construct an instance of the NotificationService class. It’s a hard-wired dependency.
That was only in place because I didn’t want to have to go to the lengths of setting up an IoC container but maybe I’ll add that in at this point. I can add a reference to (e.g.) Autofac to both my Windows/Phone projects and then I could write a little code in the (shared folder) app.xaml.cs file;
public sealed partial class App : Application { IContainer container; void InitializeContainer() { ContainerBuilder builder = new ContainerBuilder(); this.RegisterTypes(builder); this.container = builder.Build(); } #if WINDOWS_PHONE_APP void RegisterTypes(ContainerBuilder builder) { builder.RegisterType<WindowsPhoneNotificationService>().As<INotificationService>(); } #else void RegisterTypes(ContainerBuilder builder) { builder.RegisterType<NotificationService>().As<INotificationService>(); } #endif // WINDOWS_PHONE_APP public T Resolve<T>() { return (this.container.Resolve<T>()); }
Note: I got Autofac in a portable class library by taking the source and building it myself – not sure whether the package on NuGet will install into a Windows Phone 8.1 project but it doesn’t seem to for me so I built the code and referenced the binaries.
Clearly, I could improve the way (and place) in which I’m initializing that container but I make a call to that InitializeContainer method when my app starts up and then I can change my view model so that it asks the container for the service that it wants (again, I can improve this a lot too by having the container create the view model in the first place);
using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; namespace App115 { class MainPageViewModel { public MainPageViewModel() { this.notificationService = ((App)(App.Current)).Resolve<INotificationService>(); this.InvokeCommand = new DelegateCommand(OnInvokeCommand); } void OnInvokeCommand() { this.notificationService.ToastNotifyQuietlyIfPossible("Hello!"); } INotificationService notificationService; public ICommand InvokeCommand { get; private set; } } }
and now I get my platform-specific behaviour via inheritance albeit in a way that I could improve dramatically by building a small piece of “framework” to automatically resolve view models and their dependencies for views from the container.
One of the upsides is that I can definitely build the base class into some library so that I can re-use it in many places and augment it where necessary by deriving from it in projects where platform-specific tweaks are needed.
That said – what I’ve really done here is abstracted the view model from the details of the notification service and injected an implementation of that service at runtime. The fact that the Windows Phone 8.1 implementation derives from the Windows 8.1 implementation is really just an implementation detail. They could be written completely independently and the calling code wouldn’t care because it only knows about interfaces anyway. If I was thinking of this view model as being more portable across non-Windows platforms then that’d help me there because I’ve made few assumptions about the types passed in to the notification service. Generally, I’m quite a big fan of defining these dependencies by interface and then injecting implementations at runtime almost to the extent of hoping that language support might one day show up for it.
Something Slightly ‘Bigger’
All of that code was just playing around with one tiny difference in implementation between the Windows 8.1 version of the code and the Windows Phone 8.1 version of it. What if there was more of a difference between the two platforms? One place where that shows up is in the way in which file picking is done on Windows 8.1 and Windows Phone 8.1.
On Windows 8.1 you ask the user for a single file by using the FileOpenPicker.PickSingleFileAsync method.
On Windows Phone 8.1 you ask the user for a single file by using the FileOpenPicker.PickSingleFileAndContinue method.
There are differences here in that the Windows 8.1 API call is async because the file dialog pops up while the user chooses a file and then, once chosen, the expectation is that the some async operation can then be marked as complete and the originally running application code continues to run. Naturally, lifecycle events can interfere with this process if the user was to choose to move away from the app with the file picker open and maybe at some later point the OS was to terminate the app but that’s standard operating procedure for a Store app.
The Windows Phone 8.1 API call isn’t async in that sense. If you write code like this simple button handler below;
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.NavigationCacheMode = NavigationCacheMode.Required; } private void Button_Click(object sender, RoutedEventArgs e) { FileOpenPicker picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".jpg"); picker.PickSingleFileAndContinue(); Debugger.Break(); } }
then you’ll notice on line 14 that this isn’t an async call that can be “awaited” and that the debugger breaks on line 16 before the file dialog even shows on the screen so (presumably) that PickSingleFileAndContinue() API is posting some work into a queue somewhere which is then picked up “after” the Dispatcher has done its current work and that bit of magic somehow gets the OS to raise the file open picker.
If the user goes on to choose a file, how does the app get hold of the file? Via a call to the Application.OnActivated override which gets passed an instance of FileOpenPickerContinuationEventArgs (which I can’t find documented!) which carries the file in question within it.
So, there’s a “gap” between the app code asking for a file and the app code which receives that file. There’s an explanation of this up on MSDN and it’s not-so-easy to find so I’m linking to it here.
In between the call to PickSingleFileAndContinue and the call to Application.OnActivated the app will have been suspended and resumed and it may also have been terminated directly by the OS even though the user is still actively using the app. I don’t believe a similar situation exists on Windows 8.1 where the OS terminates your app as a normal part of the user simply using your app. Termination would always come as part of the user not using your app but phones operate with different levels of resources and so the phone may have to step in and terminate your app just so that the user can make use of the open file picker and then go back to your app. Of course, this should be transparent to the user and wouldn’t happen unless the phone OS was short of resources.
From the point of view of trying to write file selection code in a platform-neutral way, this is quite a bit of a platform difference to navigate around. Let’s say I want to write code to allow the user of my app to open up an image file and display the image from that file ( this keeps me out of also having to include state management for suspend/terminate in this post ).
Here’s some XAML UI for that which I can share across both projects in my share folder;
<Page x:Class="App115.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App115" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.DataContext> <local:MainPageViewModel /> </Page.DataContext> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Image Source="{Binding ImageSource}" Stretch="Uniform" /> <Button Grid.Row="1" Content="Select Image" Command="{Binding SelectImageCommand}" /> </Grid> </Page>
If I then change my previous app code to reflect this new “scenario” then I can change my view model to use a new IFileSelectionService (explicitly looked up via IoC again);
namespace App115 { using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; using Windows.Storage; using Windows.Storage.Pickers; using Windows.UI.Xaml.Media.Imaging; class MainPageViewModel : ViewModelBase { public MainPageViewModel() { this.fileSelectionService = ((App)(App.Current)).Resolve<IFileSelectionService>(); this.SelectImageCommand = new DelegateCommand(OnSelectImageCommand); } public BitmapImage ImageSource { get { return (this._imageSource); } set { base.SetProperty(ref this._imageSource, value); } } async void OnSelectImageCommand() { StorageFile file = await this.fileSelectionService.SelectPhotoFileAsync(); using (var stream = await file.OpenReadAsync()) { BitmapImage bitmap = new BitmapImage(); await bitmap.SetSourceAsync(stream); this.ImageSource = bitmap; } } public ICommand SelectImageCommand { get; private set; } IFileSelectionService fileSelectionService; BitmapImage _imageSource; } }
where the ViewModelBase class is just an implementation of INotifyPropertyChanged and I can then attempt to define that IFileSelectionService;
namespace App115 { using System.Threading.Tasks; using Windows.Storage; interface IFileSelectionService { Task<StorageFile> SelectPhotoFileAsync(); } }
and implement it;
class FileSelectionService : IFileSelectionService { public async Task<StorageFile> SelectPhotoFileAsync() { FileOpenPicker picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".jpg"); picker.FileTypeFilter.Add(".png"); StorageFile file = await picker.PickSingleCrossPlatformFileAsync(); return (file); } }
with all that code living in the shared folder. Now, if you look carefully you’d realise two things;
- This is clearly a trick because there is no PickSingleCrossPlatformFileAsync() method on a FileOpenPicker.
- This isn’t going to work in the case where the app gets terminated as part of the file picking operation
I implemented the PickSingleCrossPlatformFileAsync method using another “technique” – using extension methods. I have that core implementation of my FileSelectionService sitting in my shared project;
but there are now platform specific extensions sitting in the Windows/Phone projects;
with the Windows variant being pretty simple;
namespace App115 { using System; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Pickers; static class FileOpenPickerExtensions { public static async Task<StorageFile> PickSingleCrossPlatformFileAsync( this FileOpenPicker picker) { StorageFile file = null; file = await picker.PickSingleFileAsync(); return (file); } } }
and the Windows Phone variant being a bit less simple although you could argue that some of that comes from me writing my implementation all into one big static method;
namespace App115 { using System.Threading.Tasks; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Core; using Windows.Foundation; using Windows.Storage; using Windows.Storage.Pickers; static class FileOpenPickerExtensions { public static async Task<StorageFile> PickSingleCrossPlatformFileAsync( this FileOpenPicker picker) { StorageFile file = null; CoreApplicationView view = CoreApplication.GetCurrentView(); TaskCompletionSource<StorageFile> source = new TaskCompletionSource<StorageFile>(); TypedEventHandler<CoreApplicationView, IActivatedEventArgs> handler = (s, e) => { FileOpenPickerContinuationEventArgs continueArgs = e as FileOpenPickerContinuationEventArgs; if (continueArgs != null) { source.SetResult(continueArgs.Files[0]); } }; view.Activated += handler; picker.PickSingleFileAndContinue(); file = await source.Task; view.Activated -= handler; return (file); } } }
This code has at least one big problem with it though – it will work ok on Windows and Phone in the case where the app is not terminated as part of raising the file open picker but it will fail on the phone if the phone decides to terminate the app in order to display the file open picker in the first place. Making that work is going to require more effort and I struggle to come up with a solution that can be neatly packaged purely inside of an abstracted service like this one – it’s going to require more “knowledge” at least in the view model because if the sequence of operations is something like this;
- User taps button.
- View model invokes service to select file.
- OS terminates app.
- File selection happens.
- OS activates the app (rather than launching it) with specific arguments indicating that this is a file picker activation.
then the next sequence of events needs to be something like;
- App navigates back to the page where it was previously.
- Page creates view model.
- View model creates service.
- Service needs to be aware that there is a pending file operation to complete. To do this, it needs to be hooked into the application’s activation that happens right at start up.
- View model needs to ask service whether there is a pending file operation to complete. To do this, it needs some notion of “initialisation” at the point where the view creates it. A framework like Prism has this notion for view models but there’s nothing in the core framework or templates along those lines.
If I just test the code above debugging on the emulator then everything seems to work fine but the MSDN article I referenced above gives tips as to how to reproduce this kind of scenario in the debugger which I’ve copied below;
- In Visual Studio, right-click on your project and select Properties.
- In Project Designer, on the Debug tab under Start action, enable Do not launch, but debug my code when it starts.
- Run your app with debugging. This deploys the app, but does not run it.
- Start your app manually. The debugger attaches to the app. If you have breakpoints in your code, the debugger stops at the breakpoints. When your app calls the AndContinue method, the debugger continues to run.
- If your app calls a file picker, wait until you have opened the file provider (for example, Phone, Photos, or OneDrive). If your app calls an online identity provider, wait until the authentication page opens.
- On the Debug Location toolbar, in the Process dropdown list, select the process for your app. In the Lifecycle Events dropdown list, select Suspend and Shutdown to terminate your app but leave the a running.
- After the AndContinue operation completes, the debugger reattaches to your app automatically when the app continues.
and following those steps shows me that my app doesn’t do the right thing. Firstly, because I started the app from a blank template then there is no override for App.OnActivated and all the code which creates the UI Frame and navigates it to the main page is in the App.OnLaunched method which doesn’t even run in this scenario so no UI ends up on screen with no page and no view model and “no nothing”.
If I deal with that situation by attempting to centralise the “code which creates the main UI” into one place within my App class and making sure that it’s called in a reasonable way by both the OnLaunched method and the OnActivated method then the app’s UI gets created and navigates back to my one and only page within the app but, of course, that just creates the view model which has no knowledge that it is in the middle of a file selection operation that needs to be completed.
To even attempt to handle this is going to make me re-think my IFileSelectionService. My first attempt was;
interface IFileSelectionService { void Initialise(); Task LaunchFileSelectionServiceAsync(); StorageFile CompleteOutstandingSelectionService(); }
The idea here being that Initialise is going to have to be called really early in the app’s startup for the service to hook into the App.Activated event. The second idea being to attempt to split apart a file selection operation such that there’s a beginning piece via the Launch… method and then there’s a completion piece via the Complete… method. A sketchy implementation of this using conditional compilation might look like;
namespace App115 { using System.Threading.Tasks; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Core; using Windows.Storage; using Windows.Storage.Pickers; class FileSelectionService : IFileSelectionService { public void Initialise() { #if WINDOWS_PHONE_APP CoreApplicationView view = CoreApplication.GetCurrentView(); view.Activated += (s, e) => { FileOpenPickerContinuationEventArgs args = e as FileOpenPickerContinuationEventArgs; if (args != null) { // assume there's one file here. this.storageFile = args.Files[0]; if (this.completionSource != null) { this.completionSource.SetResult(0); } } }; #endif // WINDOWS_PHONE_APP } public Task LaunchFileSelectionServiceAsync() { FileOpenPicker picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".jpg"); picker.FileTypeFilter.Add(".png"); Task task = null; #if WINDOWS_PHONE_APP this.completionSource = new TaskCompletionSource<int>(); picker.PickSingleFileAndContinue(); task = this.completionSource.Task; #else task = picker.PickSingleFileAsync().AsTask().ContinueWith( fileTask => { // NB: not checking success etc. this.storageFile = fileTask.Result; }); #endif // WINDOWS_PHONE_APP return (task); } public StorageFile CompleteOutstandingSelectionService() { var file = this.storageFile; this.storageFile = null; this.completionSource = null; return (file); } TaskCompletionSource<int> completionSource; StorageFile storageFile; } }
this service now needs initialising very early on in the app lifecycle and then it needs to hang around so I changed my code which registered the service with Autofac to try and control the lifetime of this service;
void InitializeContainer() { ContainerBuilder builder = new ContainerBuilder(); this.RegisterTypes(builder); this.container = builder.Build(); } void RegisterTypes(ContainerBuilder builder) { builder.RegisterType<FileSelectionService>().As<IFileSelectionService>().InstancePerLifetimeScope(); }
and made sure the service was configured into the container and initialised in the App constructor;
public App() { this.InitializeComponent(); this.InitializeContainer(); this.Resolve<IFileSelectionService>().Initialise(); this.Suspending += this.OnSuspending; }
and then I changed my view model as below. It’s all a bit hacky, I’d probably want to give the view model a better “understanding” of some kind of lifecycle like the Prism guys have done with their view model which understands as it is being “navigated to/navigated from” but I have done this cheaply here by simply calling Initialise() from the constructor.
The view model then has 2 roles around file selection. It kicks off a file selection process and if the process stays around then it offers an awaitable Task and a second method to get back the resulting file. However, if the app is killed then that second method can be invoked the next time the app is run up in order to gather the file that has been provided by the OS selection process.
namespace App115 { using System; using System.Threading.Tasks; using System.Windows.Input; using Windows.Storage; using Windows.UI.Xaml.Media.Imaging; class MainPageViewModel : ViewModelBase { public MainPageViewModel() { this.fileSelectionService = ((App)(App.Current)).Resolve<IFileSelectionService>(); this.SelectImageCommand = new DelegateCommand(OnSelectImageCommand); this.Initialise(); } void Initialise() { this.ChangeImage(); } public BitmapImage ImageSource { get { return (this._imageSource); } set { base.SetProperty(ref this._imageSource, value); } } async void OnSelectImageCommand() { await this.fileSelectionService.LaunchFileSelectionServiceAsync(); await ChangeImage(); } async Task ChangeImage() { StorageFile file = this.fileSelectionService.CompleteOutstandingSelectionService(); if (file != null) { using (var stream = await file.OpenReadAsync()) { BitmapImage bitmap = new BitmapImage(); await bitmap.SetSourceAsync(stream); this.ImageSource = bitmap; } } } public ICommand SelectImageCommand { get; private set; } IFileSelectionService fileSelectionService; BitmapImage _imageSource; } }
and so the view model is entirely portable code across Windows/Phone and the underlying service is a starting point towards trying to abstract this view model from the idea that in order to perform file selection on Windows/Phone portably there’s a need to cope with the idea that the app may be actively torn down by the OS in between requesting a file selection and receiving one.
Of course, this code is very sketchy and there’d be a lot more to do to try and make it resilient to failures etc. but I thought it was more representative of something a little bit more “meaty” in terms of dealing with differences across Windows/Phone for what seems like a relatively simple operation like picking a file.
There are other APIs that take this “AndContinue” approach and it might be possible to deal with them in similar ways from the point of view of portability – i.e. defining some abstraction for the part of the API that you need and then implementing that abstraction across the Windows/Phone APIs.