Part 0: Intro
Part I: Quick sharing of code
Part II: The class library approach
Part III: We need a pattern
Part IV: Mocking out the differences
Part V: Event to command
Part VI: Behaviors for coping with orientation changes
Part VII: Tombstoning
In the previous part I talked about the MVVM pattern as a great way to share logic in your application. Your ViewModel (and Model) code can be placed in a portable class library, thus making it reusable. But, since it is not possible to put any platform specific code in your PCL, you cannot directly use platform specific services from within a ViewModel (this would make your PCL unsharable between platforms). This poses some challenges, but these can quite easily be overcome. This part in our series will be the most extensive thus far, and will contain the most advanced concepts, like inversion of control, dependency injection, and reflection. But bear with me, it will all be worth it in the end!
Let's first look at one of these platform specific behaviors: Windows Phone and Windows 8 both allow you to navigate from one page to another. The way they do this, however, is different between the two platforms. They both have a Navigate method for this, but the method exists on two different types, and also, the actual parameterlist of this method is different on each platform. You will need to find a solution to get a comparable way of navigating between pages on both platforms. You can do this by wrapping the platform specific functionality with an interface. Your ViewModels can then talk to this interface instead of talking directly to the actual navigation service of each platform.
I have for instance a MainViewModel which acts as start page for my application. From this MainViewModel you can navigate to other pages in the application. For this, the MainViewModel gets a reference to a INavigate interface through its constructor (this is my own INavigate interface, and not the one you can find in the Windows Store or Windows Phone API). Since I will be navigating in my ViewModels and not in my Views, I decided that when I navigate to another page, this is actually the same as navigating to another ViewModel. Also, I want to be able, when I navigate, to send along an additional parameter, which contains extra data for the ViewModel I navigate to.
In the above code you can see it is quite easy to navigate to another ViewModel, and send along extra data (navigate to the SudokuBoardViewModel with a specific game level). But you can also choose not to use this extra data (navigate to the SudokuRulesViewModel).
The interface itself has one extra method for navigating back.
What you will still have to do now is write platform specific implementations of the INavigate service for each platform you want to support. The good thing is, you will need to write this kind of implementations only once. After your first app, you can reuse this effort to a next application (and I will also make sure my own little API will become available on github in the next couple of weeks).
1. Navigating in a Windows Store application
Let's first look at the WindowsStoreNavigator, since this is the easiest to understand. To navigate from one page to the next, you need to make use of your Window.Current.Content property (or rootframe). In a clean Windows Store project you get a reference to this in your OnLaunched event (I set up the entire framework from the OnLaunched handler in the app.xaml.cs file). Next, we will also need some means of associating the ViewModel we wish to navigate to, with a certain View, since navigating in Windows Store applications is done to views and not to viewmodels. For this I will utilize a viewlocator (IKnowWhereYourViewIs). This viewlocator is nothing more than a dictionary that associates views to viewmodels. This way I don't need to code the binding between view and viewmodel directly in the view or in the viewmodel.
As you can see, I ask the viewlocator for the view that is associated with the viewmodel I wish to navigate to. Next I tell the rootframe to actually navigate to this view. The second parameter in the second line of the NavigateTo method instantiates my viewmodel. The inversion of control container has been set up with a list of all my viewmodels. For this I utilized TinyIoc, but you can use any wich IoC you like. The main advantage using and IoC container will be that any dependencies your viewmodel uses, will get injected by the IoC, as long as you registered them at startup.
Navigating with the extra data is a bit more complex. I created a IHandle interface which indicates wether a ViewModel can handle a certain message. If it does, it implements the IHandle
One more thing missing after navigating to a certain view, is the actual databinding between View and ViewModel. For this I created an extra base class for each View. In the OnNavigatedTo handler, we will bind the NavigationEventArgs, which in our case is our ViewModel, to the Views' DataContext.
And that's it. This allows us to navigate from one ViewModel to another, while sending along data. Also aer we now able to actually databind our ViewModel to the actual View.
2. Navigating in a Windows Phone application
Now that we can navigate in a Windows Store application, let's do the same thing for Windows Phone. As you will see, the API for navigating in Windows Phone is different from the one Windows Store applications use. In Windows Phone you navigate based on a Uri. Also, we will not be able to send along our ViewModel and request as easily. In this case, we will serialize everything before we navigate. Ie. we will serialize our ViewModel type, the actual request and the request type.
Again, we will use a MvvmPage base class, which all our views can inherit from. In its NavigatedTo event handler we will now have to (re)create our ViewModel and request. I pulled out this code to another class which implements ICreateViewModels. Since views cannot use dependency injection, I wrapped the IoC in a singleton, which can give me my viewmodelcreator.
The ViewModelCreator then uses a deserializer and some reflection to recreate the viewmodel and to make it handle the actual request.
The above code is actually the most complex part of the entire framework. An, as said, code like this, you will only have to provide once, after that your can reuse it for your next applications.
Once you got thi plumbing in place, you can navigate between ViewModels in a way that is reusable on Windows Store and Windows Phone applications. It wil make it easier to put your logic code in a shared library and to easily make changes to it that will get reflected on each platform.
Hang on for the next part, where I will talk about some extra helper classes to make MVVM work better for you.
Other parts in this series:
Part 0: Intro
Part I: Quick sharing of code
Part II: The class library approach
Part III: We need a pattern
Part IV: Mocking out the differences
Part V: Event to command
Part VI: Behaviors for coping with orientation changes
Part VII: Tombstoning
Great series of articles!
ReplyDeleteA little constructive criticism though..
The way that you are using your Ioc container looks an awful lot like the Service Locator pattern which is an Anti-Pattern. You use dependency injection correctly when your interfaces are injected into your constructors, however, your command properties use the IoC container to blindly instantiate new instances of each command if the command's private field is null. Generally, you should only use an IoC container to resolve an object graph in your App's code behind. This is known as the "Composition root". The way that you use the IoC container in the property getter to instantiate the commands that don't exist is basically identical to using the new keyword, however, since you are using your container, you are actually adding dependencies on your container rather than removing dependencies. In a multi-team environment, you can't be sure if all of the command types are registered in your IoC container. If you register all dependencies in your composition root, and then "resolve" (I use Unity, hence the terminology) the current page to be accessed, then all of the pages dependencies will be created by the container, and all of the dependencies dependencies will be created, all the way down the object graph. This is the purpose of Inversion of control. It inverts the responsibility of creating instances of your dependencies inward so that it's all handled at once in the composition root of your application. You also use the Singleton pattern which is another Anti-Pattern. I hope this helps. Otherwise, great articles! You can visit my blog where I have a few examples of Dependency injection with the Unity IoC container: http://www.refactorthis.net . Keep up the good work. Buddy James
The w