I'm probably going to get shot for this post by MVVM-purists but I'll write it anyway. :) Worth noting is that the guys over at Xamarin Forms Labs extends stuff to let you navigate easier. This is the vanilla approach.
I described the basic of MVVM using Xamarin.Forms in a previous post and I'd like to extend it a little bit to allow for navigation from the ViewModel.
The example app was dead simple and allowed for a name to be updated in a fake repository. The command defined in the ViewModel looked like this:
public ICommand Save
{
get {
return new Command (() => {
// Perform some logic
Updated = DateTime.Now.ToString();
// Store data to back end
var person = _repository.GetPerson(42);
person.Name = _name;
person.Updated = _updated;
_repository.Update(person);
});
}
}
I would now like to extend this so that I can navigate to a thank you page. The problem is that the Navigation stuff is part of the base classes that our pages inherit from. After all, the ViewModel is just a POCO that implements INotifyPropertyChanged and nothing else.
Step 1 - We need some references
We need a reference to an object implementing the INavigation interface. This is where some people get nervous that we pollute the ViewModel with stuff from the View. Well, so be it. This could also be moved to a ViewModel base class if you'd like.
public class MainPageViewModel : INotifyPropertyChanged
{
private PersonRepository _repository;
private INavigation _navigation; // HERE
public MainPageViewModel (INavigation navigation) // HERE
{
_navigation = navigation; // AND HERE
// This should be injected
_repository = new PersonRepository();
// Populate the ViewModel
var person = _repository.GetPerson(42);
Name = person.Name;
Updated = person.Updated;
}
}
We created a private field and assigned by using a reference passed to us in the constructor.
Step 2 - Pass the reference
We need to pass the reference to the Navigation object from the page.
public partial class MainPage : ContentPage
{
public MainPage ()
{
InitializeComponent ();
BindingContext = new MainPageViewModel (this.Navigation); // HERE
}
}
Step 3 - Modify the app startup to allow for navigation
To be able to navigate, we must wrap out MainPage in a NavigationPage as such:
public class App
{
public static Page GetMainPage ()
{
return new NavigationPage (new MainPage());
}
}
Step 4 - Navigate on Command
Update our Save command to navigate to the ThanksPage!
public ICommand Save
{
get {
return new Command (async () => { // HERE
// Perform some logic
Updated = DateTime.Now.ToString();
// Store data to back end
var person = _repository.GetPerson(42);
person.Name = _name;
person.Updated = _updated;
_repository.Update(person);
await _navigation.PushAsync(new ThanksPage()); // HERE
});
}
}
Conclusion
Why do we do this? The reason is to avoid writing code in the views code behind and define all logic and navigation in the ViewModel. Since the navigation is exposed as an interface, I'd say this is a valid way to do it.
It also seems like the Navigation object is a new instance on each page, so don't save a global reference since that might break something later on.