Xamarin Dev Days Lab – Prism Step 4

Well, hello from Las Vegas! I am down in LV for the Autodesk Developer Network Dev Days and the Autodesk University. It’s a pretty fun time, especially if you are involved in engineering or CAD of any kind. If you are like me, also a dev person, there is lots of cloud stuff to add in there. And now even virtual reality and augmented reality to geek out!

In any event, travel makes for weird schedules and time awake. So here I am working on navigation in Xamarin Forms projects. But first a little recap: in our last post we really dug into dependency injection and showed the power of it. We saw some concrete examples of how it can make our app better architected, and how that makes it more maintainable. Next we will take that architecture to the next level and add in navigation. You probably remember from the start of the app how we had to register pages for navigation: let’s review that.

In a normal Xamarin app, the navigation system is accessed via a singleton object. To navigate to another page , you have to provide the actual page type of the destination page to the navigation service. Right away, you can see that this could be a headache in the future: if you change the page type, you need to change it everywhere in your app that the page is referenced. Yuck. And what if you have needs that are a bit more dynamic? For example, you could have a free version of an app and a paid version. In that case your dependencies just increased by a factor of …? Well, it depends on the app, but every time you have to make that decision in your app is another level of complexity. Instead, wouldn’t it be cool if you could just determine if the app was paid or not and navigate to the appropriate page based on that status?

Prism provides a better model for navigation. Not only does Prism remove the dependency on types, it also moves the navigation brains up to the view model (where the business logic is) and away from the presentation level (where the Xamarin navigation system resides). And this all begins in the RegisterTypes method of the App object. So let’s start with what we have there.


/// first we have the register types
protected override void RegisterTypes()
{
    // non nav registration here
    Container.RegisterTypeForNavigation
        <View.SpeakersPage, ViewModel.SpeakersViewModel>();
}

/// and our initial navigation
protected override void OnInitialize()
{
    NavigationService.NavigateAsync("SpeakersPage");
}

The above registers the SpeakersPagae for navigation and associates it with the SpeakersViewModel. There is also an optional parameter in the call to associate the registration with a key. Since we didn’t provide a key, Prism made one for us based on the name of the page. When you call NavigateAsync, you provide the key and the Prism navigation system looks up what is associated with the key and instantiates the appropriate page and view model.
We can improve this by specifying our own key. If we do that, in every place we navigate to the page, simply changing the initial registration will allow us to put in a different page. We won’t have to go to every place and change the call. I like to create a static class with the page keys so that I can reduce my use of magic strings.

public static class PageKeys
{
    public const string Speakers = "speakers";
    public const string Details = "details";
}

And now we can update the two functions above:


protected override void RegisterTypes()
{
    // non nav stuff here

    Container.RegisterTypeForNavigation
        <View.SpeakersPage, ViewModel.SpeakersViewModel>
        (PageKeys.Speakers);
}

protected override void OnInitialize()
{
    NavigationService.NavigateAsync(PageKeys.Speakers);
}

 
So that is looking pretty cool. Let’s pretend there is a paid and free version of the app and adjust our registration:

protected override void RegisterTypes()
{
   if (IsPaidApp)
   {
        Container.RegisterTypeForNavigation
          <View.SpeakersExPage, ViewModel.SpeakersExViewModel>
          (PageKeys.Speakers);
   }
   else
   {
       Container.RegisterTypeForNavigation
         <View.SpeakersPage, ViewModel.SpeakersViewModel>
         (PageKeys.Speakers);
   }
}

Awesome! Just by using the key instead of the page type name, we made our app even more flexible and easier to maintain. None of our in app navigation will change, we always just call:


NavigationService.NavigateAsync(PageKeys.Speakers);

What else is important in this? Well I mentioned this before, but the navigation system is rooted into the presentation layer of the code. But navigation is really about business logic. The Prism navigation service abstracts out the presentation and the service is passed around as an interface. For unit testing of your view model, you can mock the interface and your view model won’t know or care.

You probably already know how the view model gets the navigation service (dependency injection, constructor parameter), but just in case it is late or you don’t remember every single detail of the previous posts, we just put the dependency into the constructor and the dependency injection container injects it for us. You can see this in the code. If you are looking at the code and saying: wait, INavigationService is not registered anywhere in your code, you are right. The App base class does it for us because they assumed that you were going to want that service. Dig into the Prism code if you want to find out more.

The last thing I want to bring up with respect to Prism navigation is how to pass data during navigation. When you use the NavigationService.NavigateAsync method, the second parameter is a NavigationParameters object, and you can pass pretty much anything in in. Let’s dive in.

In this example, I want to know when the SpeakersPage is navigated to the first time. So in the app object, I am going to add that parameter to the initial navigation in the OnInitialize method.


protected override void OnInitialize()
{
    var nps = new NavigationParameters();
    nps.Add("first", true);
    NavigationService.NavigateAsync(PageKeys.Speakers, nps);
}

How do we consume this data? And where do we consume it? This data is really business logic data so we want to consume it in the view model. To get the data up to the view model we have to have our view model implement the INavigationAware interface. This interface has two methods: OnNavigatedFrom and OnNavigatedTo. I normally like to implement this in the BaseViewModel class so that all the view models have it already and I can also setup some generic functionality if I want.

public class BaseViewModel : BindableBase, INavigationAware
{
    /// ... other base stuff goes here

    public virtual void OnNavigatedFrom(
        NavigationParameters nps)
    {
    }

    public virtual void OnNavigatedTo(
        NavigationParameters nps)
    {
    }
}

So now in my SpeakersViewModel, I will override the OnNavigatedTo method to implement looking for first run:

public class SpeakersViewModel : BaseViewModel
{
    /// ... existing stuff here

    public override void OnNavigatedTo(
        NavigationParameters nps)
    {
        base.OnNavigatedTo(nps);

        if (nps == null || !nps.ContainsKey("first"))
            return;

        bool firstLoad = (bool) nps["first"];
        if (firstLoad)
        {
            /// do first functionality here
        }
        else
        {
            /// do something else
        }
    }
}

But how does that actually work? What happens is that the navigation service is aware of the page creation. And as part of the page creation, we know that the view model will be automatically injected. The navigation service will look at the view model object that was injected and see if it implements the INavigationAware interface. If it does, it will call the OnNavigatedTo method. More awesomeness!

Well, that is it for navigation. You can see full-on code at the usual place in my GitHub repo under Step 04.
Enjoy!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s