Architecting WP7 - Part 3 of 10: Enough Architecture (or Where is my VM)

  • Oct 13, 2010 at 2:19 PM
  • Shawn Wildermuth
  • 8 Comments

Windows Phone 7

Blogging everyday is getting exhausting.  But seriously folks (and don't forget to tip your wait staff)... Here in day three of Architecting Windows Phone 7 applications, I want to talk about locating the view-model. If you've missed the past parts of the series, you you can visit them here:

I will take the assumption that you've read about the MVVM pattern in Silverlight (my MSDN article is here if you want a refresher).

The phone is a small platform (no pun intended) so your Silverlight apps may be smaller than you might imagine. In addition, you are tied to the page navigation story (a good thing IMHO) so you may want to look at handling view-models in a different way.

Luckily there are toolkits that help (e.g. MVVMLight and Caliburn Micro) but before you dive into the toolkits, I think it's important to get your head around the problem. If you're building a single-page app (or more importantly, a single View-Model app), then its easy. You can locate the VM either in the View (which I don't prefer) or have a factory to deliver it (e.g. expose it at the App class or other instantiation method). As you start to look at the nature of the location problem, it starts to look and feel like the routing problem (a la MVC's Routing Framework).

I think it comes down to a matter of taste unfortunately. Some frameworks seem to want to supply a VM to a View; and others want the VM to tie to the View. Having something like a mediator handle 'marrying' the two is still an approach I prefer.  But what does that mean for the phone?  Well, I tend to try to stay pragmatic. Since most (not all) phone applications are going to be small (by the nature of the platform) the more plumbing you add to make this happen, the less helpful it is. Sometimes ceremony of building an IoC container and plumbing up a VM locator (or mediator) is just to much trouble for these smaller applications. Certainly using something like MVVMLight's Locator or CaliburnMicro's Conductor makes sense when you're building a large application, but I don't see it as required in most phone applications. So I am going to let you know a secret...I cheated.

public partial class App : Application
{
  private static UserViewModel viewModel = null;

  public static UserViewModel ViewModel
  {
    get
    {
      // Delay creation of the view model until necessary
      if (viewModel == null)
        viewModel = new UserViewModel();

      return viewModel;
    }
  }
  // ...
}

Instead of building up plumbing, I just made it part of the Application class. Sure, its cheating, but for my application, there was only a single view-model.  Even if there were more than one VM, it was likely to be sub-VM's so the main VM could hand-off the VM. In this pragmatic sense, for a small discrete application, this works just fine and lets me write tests against the different parts of the application. Because its part of the Application (though I could have created it as part of the Application Lifecycle objects), my views can request the data directly like so:

public partial class LandingPage : PhoneApplicationPage
{
  // Constructor
  public LandingPage()
  {
    InitializeComponent();

    // Set the data context of the listbox control to the sample data
    DataContext = App.ViewModel;

  }
  // ...
}

Note that I am not holding onto a hard reference to the type (though I could mitigate this with an interface). The rest of the work on this page is done directly through data binding so the duck-typing experience remains strong.

So what about other views (e.g. Pages)? I continue to use the view-model to give me the data these pages need. I could have created a method that passed in data from a query string, but in my case the application was simple enough that the view-model could expose a property that was the current item to edit (as shown here in a detail page):

public partial class EntryPage : PhoneApplicationPage
{
  Reading _currentReading = null;

  public EntryPage()
  {
    InitializeComponent();
    Loaded += (s, e) =>
      {
        if (_currentReading == null)
        {
          // Ensure that application's ViewModel is tied to this view
          _currentReading = App.ViewModel.CurrentReading.Clone();
          ParseTags();
          DataContext = _currentReading;
        }
      };
  }
  // ...
}

Is this super-clean?  No.  Is it clean enough? I think so. I debated with myself about how much architecture I needed here...and I decided that I had enough (I have a full separation of concerns, but no IoC and no locator service).

This approach works for me as the size of what I've built isn't huge yet. As applications increase in size, the architecture will need to as well. Using techniques like messaging (a la MVVMLight's Messenger) and routing (a la Caliburn Micro's Conductors) could come into play pretty quickly.  But since memory and app size is an issue on the device, I am looking for enough architecture to get the code working and to give me the flexibility to modify the applications going forward. I think that's the important part of the story on the phone to me. I am sure there will be dissenters in the comments, but that's ok...I like that ;)

 

 

Comments

Gravatar

JasonBsteele Thursday, October 14, 2010

I've been struggling with this too. Exposing the WM from App seemed wrong but I didn't want to add the overhead of a locator for 5 pages.

Thanks for the honesty... and sanity :)

Gravatar

Paul Thursday, October 14, 2010

I've taken the same approach for an app I'm building and in fact, this appears to be part of the out of the box template for pivot/pano apps.

However, rather than set DataContext at the page level, I set it on the rootframe (PhoneApplicationFrame). This negates any need for code behind to wire up the datacontext per page. For a typical master/details page navigation scenario, I use the properties like CurrentItem as per your example which the detail page can bind to directly.

Gravatar

Shawn Wildermuth Thursday, October 14, 2010

Paul,

I love the idea of having it from the Frame. Never thought of that. I'll have to try that.

Gravatar

AntRemo Friday, October 22, 2010

Forgive me for sounding stupid :)

I love the concept of setting it in one place, however, I'm not following how the DataContext can be set on the rootframe.

The way I'm understanding this, I would need to modify the PhoneApplicationFrame class, however, this appears to be part of the Assembly Microsoft.Phone.dll and is not defined as a partial class.

Thanks for the help. :)
Ant


Gravatar

Shawn Wildermuth Friday, October 22, 2010

AntRemo,

The app class has a reference to the frame (via RootFrame) so you can do:

((App)App.Current).RootFrame.DataContext = vm;

Gravatar

Tony Merante Friday, October 22, 2010

How does Blend like it when you set the DataContext in either the Page or Application? Does this make the app not "Blendable"?

Gravatar

Shawn Wildermuth Friday, October 22, 2010

Tony,

It doesn't seem to affect as the setting happens outside Blend's world (e.g. not in the constructor).


Leave a Comment

*
*
*