26 December 2012

Handling Windows Phone 8 NFC startup events using the MVVMLight Messenger

wp_ss_20121226_0002As regular readers of this blog know, I am currently playing around with NFC on Windows Phone 8. I peeked a little in the Bluetooth app to app sample at MSDN to understand how to find a ‘peer’. A peer is defined as the same app, installed on a different phone. So basically you go around installing the app on two phones, start it on phone 1, make sure it calls PeerFinder.Start(), tap the phones together and presto - the popup as displayed in the image on the right appears. If you check ‘open app’ – or whatever it shows in the language you have selected – the app is started on the second phone as well.

You can distinguish between an app being started by itself, or with the “open app” button by overriding the OnNavigatedTo method from the app’s main startup page. If the app is all started up by itself, the NavigationEventArgs.Uri just contains the name of the main page, for instance “MainPage.xaml”. But if it is started by an NFC event, the Uri ends with the following rather funny long string:

ms_nfp_launchargs=Windows.Networking.Proximity.PeerFinder:StreamSocket

Annoyingly, the OnNavigatedTo event is only available inside the page’s code. If you want to handle this the MVVM way, you want this to be taken care of by a view model or a model, not by the page’s code behind.

I have gone the following route. First, I define a little extension method that easily helps me to determine if the app was initiated by the user or by an NFC event:

using System.Windows.Navigation;

namespace Wp7nl.Devices
{
  public static class NavigationEventArgsExtensions
  {
    public static bool IsStartedByNfcRequest(this NavigationEventArgs e)
    {
      var isStartedByNfcRequest = false;
      if (e.Uri != null)
      {
        isStartedByNfcRequest = 
          e.Uri.ToString()
          .Contains("ms_nfp_launchargs=Windows.Networking.Proximity.PeerFinder:StreamSocket");
      }
      return isStartedByNfcRequest;
    }
  }
}

And as I don’t like to pass bare events around, I make a little wrapper class to use as a message:

using System.Windows.Navigation;
using Wp7nl.Devices;

namespace PullTheRope.Logic.Messages
{
  public class NavigationMessage
  {
    public NavigationEventArgs NavigationEvent { get; set; }

    public bool IsStartedByNfcRequest
    {
      get 
      { 
        return NavigationEvent != null && NavigationEvent.IsStartedByNfcRequest();
      }
    }
  }
}

And it contains a convenient shortcut method as well. Then the only thing you have to do is indeed override the OnNavigatedTo method, like this:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
  base.OnNavigatedTo(e);
  Messenger.Default.Send(new NavigationMessage {NavigationEvent = e});
}

This is the only ‘breaking’ of the purist's MVVM approach – but as the event is nowhere else available, it’s also the only way to get it done. Sometimes you have to take the pragmatic approach. Anyway, somewhere in your model or view model you define which method should be called when the message is received

Messenger.Default.Register<NavigationMessage>(this, ProcessNavigationMessage);

a method to trap it

private void ProcessNavigationMessage(NavigationMessage message)
{
  // whatever – we are back in MVVM territory
}

and I trust you can take it from there ;-) .

Be aware the events generated are generated outside the GUI thread, so if you try to data bind properties changed from the event method, you might run into cross thread access errors. Deployment.Current.Dispatcher.BeginInvoke is your friend, then.

No comments: