This is a continuation from the first post. You can find all of the code in my GitHub repository under step 2.
Viewmodels
I think most of us know what a viewmodel is and have an understanding of the MVVM pattern. If you don’t know, or need a refresher, there will probably be enough information in here for you to search around on.
The starter app already has one view model in it called SpeakersViewModel. You will remember it as the class that implements INotifyPropertyChanged. This interface is what tells the data binding system to update the UI, and when the UI changes, to update the property in the view model. Every view model needs to implement this interface, so right away, I hope you can see that we would like to move that functionality into a base class so as not to keep redoing it. And, as you might guess, Prism already has such a base class! I still like to make my own base class though, and include any app specific properties that I use a lot.
Below is a snippet of the SpeakersViewModel showing the implementation of INotifyPropertyChanged and how to use it from within the view model itself.
public class SpeakersViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string name = null) =>
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(name));
private bool _isBusy = false;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
OnPropertyChanged();
}
}
/// other properties go here
}
Let’s refactor the above view model to use the Prism helpers. First we will make our own app specific viewmodel base and derive from the Prism MVVM base class. Just for fun we will put the IsBusy property into the base class just to show the property in action.
public class BaseViewModel : BindableBase
{
BaseViewModel()
: base()
{
}
private bool _isBusy = false;
public bool IsBusy
{
get { return _isBusy; }
set { SetProperty<bool>(ref _isBusy, value); }
}
}
public class SpeakersViewModel : BaseViewModel
{
SpeakersViewModel()
: base()
{
}
/// put other speakers functionality here
}
I think the above is pretty straight forward. We created a base class to hold common functionality and then redid our view model. We are still deriving from INotifyPropertyChanged (via BindableBase), and we still have the IsBusy property. If you go to the code in Step02 you can see the class in its entirety.
This brings us to our next problem: how do we get the view model instantiated in the class? In the case of the dev days sample app, it is being “newed-up” in the Page code-behind and being set manually. This is fine for very simple view models, but if we need services added to the view model, we are going to find it a bit more painful.
In the Prism world, you would let Prism take care of instantiating the view model for you, and by registering your services in the Container (see the App class), they will be added to the view model automatically. The way Prism manages this is by using an attached property. If the property is set to true, it will use a naming convention to find the view model type. Instead of using the “new” operator directly, it will use the UnityContainer to create it for you. The UnityContainer will determine (via reflection) if the view model depends on any services, and if so, inject those services into the view model for you. Once it has been created, it will attach it to the BindingContext of the page. What does this look like?
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="DevDaysSpeakers.View.SpeakersPage"
Title="Speakers">
</ContentPage>
The important line is the one containing AutowireViewModel=”True”. First we need to setup the namespace so that we can attach the AutowireViewModel property to the page. If that value is true, the logic behind the property will use naming conventions to find the view model class and then create it and bind it. The default conventions is that all pages are stored in the “Views” folder and the view models are stored in the “ViewModels” folder. The full name of the view model will be the name of the page with “ViewModel” appended. In the above example, Views.SpeakersPage will become ViewModels.SpeakersPageViewModel.
We also need to remove the view model and binding that we did in the page code-behind. In the SpeakersPage constructor, remove everything except for the constructor. It should now look like this:
public partial class SpeakersPage : ContentPage
{
public SpeakersPage()
{
InitializeComponent();
}
}
If you were paying really close attention, you would notice that in our dev days app, we are actually using the “View” and “ViewModel” folder, so we are probably going to have to override the default functionality. There are two ways to do this: first is to override the default view model resolver function. This is a bit more involved, and I am going to look at this functionality in another blog post. The other method is to specify the view model type when you register the page for navigation. In your App.RegisterTypes function, change your SpeakersPage registration to look like this:
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation
<View.SpeakersPage,ViewModel.SpeakersViewModel>();
}
Now any time we navigate to the SpeakersPage, the SpeakersViewModel will be created and injected. If you run the app now, you should see that the UI looks exactly the same, and that you can click on the sync button and it displays the results on the main page.
Recap
To recap, what we have done is changed how the view models are created and made use of the Prism base view model. We also made use of the Prism technique of automatically creating and injecting the view model into the page BindingContext. Our next step will be to abstract out the the web api service that is inside the SpeakersViewModel so we can show how the dependency injection works within Prism: more importantly, it makes your app more testable and maintainable.
You can find the code under Step 02 of my GitHub repository.