Metro: navigation service
In the same post we have already outlined before Matteo introduces the concept of navigation in a WinRT/Metro application, the solution outlined by Matteo is clean and neat even if it basically force you to fallback to a view model locator to attach ViewModel(s) to View(s) since the built-in Frame has its own logic (currently not pluggable as in Silverlight) to create views instances. View model locator is absolutely a valid solution, even if I do not really like it, but it is just a matter of opinions.
One thing missing in the Matteo’s sample is that “MVVMing” you want to have full control of what’s going on in the ViewModel and when using a navigation infrastructure you need navigation callbacks in the view model.
Going on with the Matteo’s implementation a really neat solution is to use behaviors in the view to attach to the navigation events and to call navigation callback methods in the ViewModel, the main problem is that behaviors does not (yet?) exists in the xaml for WinRT. Radical introduces this support with its own behavior implementation:
<TextBlock HorizontalAlignment="Left" Margin="91,62,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"> <bhv:Extensibility.Behaviors> <foo:SampleBehavior /> </bhv:Extensibility.Behaviors> </TextBlock>
where the SampleBehavior is something like:
namespace Radical.Samples { public class SampleBehavior : Topics.Radical.Windows.Behaviors.RadicalBehavior<TextBlock> { protected override void OnAttached() { } protected override void OnDetached() { } } }
Now…we have solved all the scenarios, but if we want to go deeper we can build our own navigation infrastructure whose purpose is:
- deeply understand how stuff works under the hood;
- get rid, if you want, of the view model locator since you gain control over the view instance creation;
It is not an easy task, well it is not complex but there are several things that you have to take care of, because you have to support:
- Navigation stack: as for the Windows Phone apps Metro as the concept of backward navigation so the navigation service must be able to keep track of the navigation stack in order to be able to support a “navigate back” request;
- Navigation caching: in order to behave as the built-in Frame a back navigation request should not recreate instances of the “world”, but the previous view should be retrieved from the cache;
- Stack persistence and restore: since we keep track of the navigation stack we need to be able to persist it in order to respond successfully to application suspend requests and we also need, much more important, to be able to restore the navigation stack in order to display to the user the same view where the user left;
- Suspension management: the previous point requires us to implement a full suspension management system so keep track of the current application state across suspension cycles;
All those systems allows us to do something like this:
class MainViewModel : AbstractViewModel, IExpectNavigatedToCallback, IExpectNavigatingAwayCallback { readonly INavigationService ns; public MainViewModel( INavigationService ns ) { this.ns = ns; this.GoToNextPage = DelegateCommand.Create() .OnExecute( o => { this.ns.Navigate( "/Basic/Foo/Bar", "hi there!" ); } ); } public ICommand GoToNextPage { get; private set; } void IExpectNavigatedToCallback.OnNavigatedTo( NavigationEventArgs e ) { } void IExpectNavigatingAwayCallback.OnNavigatingAway( NavigatingAwayEventArgs e ) { } }
I have highlighted the part to notice:
- the navigation infrastructure allows you to navigate by URI (currently there is no UriMapper but it is on the roadmap);
- the navigation infrastructure gives you an easy way to get plugged in the navigation pipeline;
You can still benefit of the view model locator pattern, if you like, and you gain all the goodies of a navigation service implementation that is thought for a world based on Model View ViewModel.
.m