Xamarin, Xamarin.Forms

A Responsive ViewModel – Part I

Last time, we talked about what’s missing in MVVM support with Xamarin.Forms, particularly in the realm of the ViewModel. We may have gotten lost in the weeds a bit, so let’s focus a bit more here. Our focal point will be creating a ViewModel that’s more responsive, including support for complex type parameters during navigation and responding to view appearing/disappearing events. We’ll use the previous article as a foundation for what we will build on today.

Recall the CreatePage method we created previously in ApplicationBase to contain the boiler plate that we need to tie together the bits contained in a Page that we would like to support in a ViewModel:

public Page CreatePage<TPage, TViewModel>(bool wrapInNavigationPage = false)
    where TPage : ContentPage
    where TViewModel : ViewModelBase
{
    var vm = default(TPage);
    var page = default(TViewModel);

    vm.SetWeakPage(page);
    page.BindingContext = vm;

    if (wrapInNavigationPage)
    {
        return new NavigationPage(page);
    }

    return page;
}

Here, we have already given ourselves the foundation and pieces necessary to support navigation parameters and allowing the ViewModel to respond to events that occur within the Page. This is because we have constrained both the creation of the Page and the ViewModel to our own method where we can define the lifecycle we desire.

Our first step is not to code, but to define how to represent our navigation parameters. Consider that the consumer would maybe like to send multiple parameters of the same type, e.g. int. There is no need to create our own distinct type for this. A Dictionary<string, object> would suffice as the consumer would use the string key to define unique parameters and the object value to contain the actual parameter they would like to pass.

Secondly, to support navigation parameters, we will create a more verbose API surface for CreatePage to allow different combinations of our navigation parameters and the boolean wrapInNavigationPage to be supplied. We do not want to force the consumer to use one, both or either parameter. Therefore, we’ll create an API surface that looks like the following.

Page CreatePage<TPage, TViewModel>(Dictionary<string, object> navigationParams = null)
Page CreatePage<TPage, TViewModel>(bool wrapInNavigationPage, Dictionary<string, object> navigationParams = null)

Now that we have an API that allows the consumer to provide navigation parameters, we need the ability to pass this information into the ViewModel. We do not want to inject these into the constructor of the ViewModel since it’s most likely/possible that the consumer’s implementation of ViewModelBase could do complex or long tasks with that information. A constructor should always be allowed to be constructed safely and as quickly as possible and we should assist the consumer in that approach. To that end, we can add a new method to our ViewModelBase that the consumer can choose to implement. Since we want it to be a choice for the consumer, we will favor a virtual method instead of an abstract one.

public virtual void Initialize(Dictionary<string, object> navigationsParams = null) { }

What’s left to wrap up the lifecycle of this simple implementation is to pass the navigation parameters we received in our CreatePage method to our Initialize method of our ViewModel. To do that, we only need to add the following line to our CreatePage method, which I’ll demonstrate more verbosely in a moment.

vm.Initialize(navigationParams);

Supporting Async/Await

Based on my experience thus far with Xamarin.Forms, it is a common demand and expectation that we would want to support async/await in this “initialize” flow. The result of passing navigation parameters can certainly lead to operations that would require it. To that end, we will update our virtual method Initialize to support async/await with a default implementation.

public virtual async Task Initialize(Dictionary<string, object> navigationsParams = null) 
            => await Task.CompletedTask;

This seemingly small change causes us to chain this async support up through our API and into our CreatePage method, where we will also need to support asynchronous operations. As such, our CreatePage implementation will now be implemented as follows.

public Task<Page> CreatePage<TPage, TViewModel>(Dictionary<string, object> navigationParams = null)
    where TPage : ContentPageBase
    where TViewModel : ViewModelBase
    => CreatePage<TPage, TViewModel>(false, navigationParams);

public async Task<Page> CreatePage<TPage, TViewModel>(bool wrapInNavigationPage, Dictionary<string, object> navigationParams = null)
    where TPage : ContentPageBase
    where TViewModel : ViewModelBase
{
    var vm = default(TViewModel); // future article
    var page = default(TPage); // future article

    vm.SetWeakPage(page);
    await vm.Initialize(navigationParams);

    page.BindingContext = vm;

    if (wrapInNavigationPage)
    {
        return new NavigationPage(page);
    }

    return page;
}

There’s one more piece of the puzzle for added simplicity for anyone consuming this. That piece is to add support to our PushAsync methods in ViewModelBase so the consumer can avoid invoking CreatePage directly and navigating to the newly created Page. While optional, this leads to cleaner implementations within derived classes of ViewModelBase. As we did with CreatePage, we’ll create additional overloads of PushAsync to support different combinations of parameters.

public Task PushAsync<TPage, TViewModel>(bool animated = true)
    where TPage : Page
    where TViewModel : ViewModelBase
    => PushAsyncInternal<TPage, TViewModel>(null, animated, false);

public Task PushAsync<TPage, TViewModel>(Dictionary<string, object> navigationParams, bool animated = true)
    where TPage : Page
    where TViewModel : ViewModelBase
    => PushAsyncInternal<TPage, TViewModel>(navigationParams, animated, false);

public Task PushModalAsync<TPage, TViewModel>(bool animated = true)
    where TPage : Page
    where TViewModel : ViewModelBase
    => PushAsyncInternal<TPage, TViewModel>(null, animated, true);

public Task PushModalAsync<TPage, TViewModel>(Dictionary<string, object> navigationParams, bool animated = true)
    where TPage : Page
    where TViewModel : ViewModelBase
    => PushAsyncInternal<TPage, TViewModel>(navigationParams, animated, true);
private async Task PushAsyncInternal<TPage, TViewModel>(Dictionary<string, object> navigationParams = null, bool animated = true, bool modal = false)
    where TPage : Page
    where TViewModel : ViewModelBase
{
    Page page = await CurrentApplication.CreatePage<TPage, TViewModel>(navigationParams);

    if (modal)
    {
        await Navigation.PushModalAsync(page, animated);
    }
    else
    {
        await Navigation.PushAsync(page, animated);
    }
}

Now that our implementation is in place, usage of this feature would be as follows.

public class ViewModelA : ViewModelBase
{
    public async Task NavigateToPageB()
    {
        var p = new Dictionary<string, object>()
        {
            ["Key"] = "value"
        };
        await PushAsync<ViewB, ViewModelB>(p);
    }
}

public class ViewModelB : ViewModelBase
{
    public override async Task Initialize(Dictionary<string, object> navigationsParams = null) 
    {
        if (navigationsParams?.ContainsKey("key") ?? false)
        {
            // something super important is going on here...
        }
    }
}

Bonus: Navigation Parameters on Pop

Adding support for navigation parameters when popping is relatively simple as it follows the same pattern that we just implemented. In this example, we’ll just handle the case of PopAsync and not PopModalAsync or PopToRootAsync. Although, those would follow the same approach.

First, we define the method on our ViewModelBase to override to respond to being popped to.

public virtual async Task PoppingTo(Dictionary<string, object> navigationsParams = null) => await Task.CompletedTask;

Next, we would update our pass-through method PopAsync and ViewModelBase to handle passing these navigation parameters. A unique check we should do here is to ensure that we have another ViewModelBase in the stack to pop to.

public Task<Page> PopAsync(Dictionary<string, object> navigationParams = null, bool animated = true) 
{
    if (NavigationStack.Count > 1)
    {
        var vm = NavigationStack[NavigationStack.Count - 2].BindingContext as ViewModelBase;

        if (vm != null)
        {
            await vm.PoppingTo(navigationParams);
        }
    }

    Page popped = await Navigation.PopAsync(animated);

    return popped;
}

That’s it! Usage of this feature would be as follows.

public class ViewModelB : ViewModelBase
{
    public async Task GoBack() 
    {
        var p = new Dictionary<string, object>()
        {
            ["Key"] = "value"
        };
        await PopAsync(p);
    }
}

public class ViewModelA : ViewModelBase
{
    public override async Task PoppingTo(Dictionary<string, object> navigationsParams = null)
    {
        // something important is going on
    }
}

You can find the entire project on Github:
https://github.com/jacob-maristany/Xamarin.Forms.Mvvm


Next time on…

In Part II of this series, we’ll build on what we’ve learned to add support to our responsive View Model for handling responses to view appearing/disappearing events.

Xamarin, Xamarin.Forms

What’s Missing in Xamarin.Forms MVVM Support

One thing that has always bothered me about Xamarin.Forms development is that it has an opinionated encouragement for MVVM, but its supported implementation for MVVM has always felt incomplete. This has caused a number of supporting MVVM frameworks to sprout up.

I will not argue against using MVVM for Xamarin.Forms applications as I do believe it is the most efficient and productive way of developing with it. This is largely due to that “opinionated encouragement for MVVM” I mentioned initially, which is manifested largely by its support and encouragement to use Data Binding: the magic-sauce for MVVM support.


Aside:
For those interested in developing with Xamarin.Forms without MVVM, Fabulous is an officially supported framework to build Xamarin.Forms applications with F# and MVU (Model-View-Update).
https://fsprojects.github.io/Fabulous/


After developing with a few different MVVM frameworks over the years, I came to desire to use something simpler and much closer to the base API surface and functionality that Xamarin.Forms provides. This was driven by a few different factors:

  • Bringing on new developers and passing off applications to support teams can be costly. Additional frameworks lead to the overhead of training people on these frameworks and how to use them properly.
  • Any additional MVVM framework introduces additional risk for issues and bugs, some of which manifest in fundamental changes in how Xamarin.Forms is supposed to operate.
  • I found the main benefit(s) of using these frameworks were actually unnecessary in the vast majority of cases.

In essence, what I realized was that there was little benefit provided by these frameworks. Any benefit that did occur was generally outweighed by the points above.

My focus here is not to call out any specific issues with any specific frameworks, but to provide a viable and productive alternative by filling in the gaps yourself. If you have interest in additional posts about the various MVVM frameworks, please reply in the comments below or hit me up on Twitter.

In order to solve this issue, we must first define what these “gaps” are. Generally, I found they revolved around the missing support for an official Xamarin.Forms ViewModel. For anyone doing MVVM long enough, it’s always been expected of you to write your own ViewModelBase for INotifyPropertyChanged support.


Aside:
I’m hoping the introduction of default interface implementations with C# 8 will cause Microsoft to add a basic implementation to System.ComponentModel.INotifyPropertyChanged in the future.


Here is a common implementation of INotifyPropertyChanged and the one I am using.

public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
        {
            return false;
        }

        storage = value;
        OnPropertyChanged(propertyName);

        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

What I largely found to be lacking is support for Navigation and DisplayAlert outside of the Page as well as passing arguments to a ViewModel during navigation. Honestly, this is primarily what I used other MVVM frameworks for. My challenge was to solve this in the simplest way possible without changing how these features of Xamarin.Forms function.

A heavy-handed approach would be to allow all navigation and alert displays to still occur in the Page class, but to provide additional Data Binding and support between the ViewModel and the Page. This seemed like a terrible way to go…

In the end and for simplicity, I settled on a necessary evil: to provide a cyclic reference. This means that the Page has a reference to the ViewModel as it is the BindingContext for the Page while the ViewModelBase has a reference to the Page in order to access the API surface of the Page (albeit in a controlled manner). The “evilness” was offset by referencing the Page as a WeakReference<T>.

public abstract class ViewModelBase : NotifyPropertyChanged
{     
    private WeakReference<ContentPage> _page;
    protected ContentPage Page => _page.TryGetTarget(out ContentPage target) ? target : null;
    public void SetWeakPage(ContentPage page) => _page = new WeakReference<ContentPageBase>(page);
}

Now, we have a ViewModelBase that has an API surface that allows us to specify a Page for the ViewModel. This gives us the gift of being able to access and aggregate the API surface of the Page within the ViewModel. You’ll notice that I have the page marked as protected, so the full API surface of it is accessible, but this can easily be made private (and arguably should be).

The boiler plate for creating a page in this way may look like this:

var page = new MyPage();
var vm = new MyViewModel();
vm.SetWeakPage(page);
page.BindingContext = vm;

In a future post, well update to add in dependency injection and complex-type parameter passing.

Since we now have the Page accessible within the ViewModel, we can provide controlled access to the Navigation instance of the Page and the API surface for displaying alerts, all without having to create separate service abstractions. Starting with alerts, we’ll add the following to our ViewModelBase:

public Task DisplayAlert(string title, string message, string cancel)
    => Page?.DisplayAlert(title, message, cancel);

public Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
     => Page?.DisplayAlert(title, message, accept, cancel);

public Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
    => Page?.DisplayActionSheet(title, cancel, destruction, buttons);

For Navigation, things become more complex. The Navigation APIs expect an instance of a Page. We shouldn’t expect an implementing ViewModel to use our boiler plate over-and-over again to construct a Page to pass through to the Navigation’s API call. Therefore, we’ll create a method that will do the construction for us. However, we’ll also need access to this from the Application class when we bootstrap the Forms application. Therefore, I put the implementation of this in an ApplicationBase class, which I also used for other reasons that I’ll get into in future posts.

public abstract class ApplicationBase : Application
{
    public Page CreatePage<TPage, TViewModel>()
        where TPage : ContentPage
        where TViewModel : ViewModelBase
    {
        var vm = default(TPage);
        var page = default(TViewModel);

        vm.SetWeakPage(page);
        page.BindingContext = vm;
        
        return page;
    }
}

Side Note:
The main reason I created the ApplicationBase class was to re-use this implementation in multiple apps. The point I’m trying to stress here is that it is not difficult to bridge the MVVM gaps created by Xamarin.Forms without a separate MVVM framework. Yes, I see the irony in that it appears that I am building Yet Another MVVM Framework. 🙂 To that end, I am not publishing this as a standalone framework and am hoping to find a way to bolster the core Xamarin.Forms project with these lessons.


With our CreatePage method in place, we can add a controlled API surface to our ViewModelBase for Navigation that utilizes it. As mentioned, this is not necessary, but still gives us access closer to the raw Xamarin.Forms API surface and functionality. You’ll also noticed I slimmed down the Navigation calls a bit by using default parameters.

protected ApplicationBase CurrentApplication => Application.Current as ApplicationBase;

private INavigation Navigation => Page?.Navigation ?? Application.Current.MainPage.Navigation;

public Task<Page> PopAsync(bool animated = true) => Navigation.PopAsync(animated);
public Task<Page> PopModalAsync(bool animated = true) => Navigation.PopModalAsync(animated);
public Task PopToRootAsync(bool animated = true) => Navigation.PopToRootAsync(animated);
public void RemovePage(Page page = null) => Navigation.RemovePage(page ?? Page);

public Task PushAsync<TPage, TViewModel>(bool animated = true)
    where TPage : ContentPage
    where TViewModel : ViewModelBase
        => PushAsyncInternal<TPage, TViewModel>(null, animated, false);

public Task PushModalAsync<TPage, TViewModel>(bool animated = true)
    where TPage : ContentPage
    where TViewModel : ViewModelBase
        => PushAsyncInternal<TPage, TViewModel>(null, animated, true);

private Task PushAsyncInternal<TPage, TViewModel>(bool animated = true, bool modal = false)
    where TPage : ContentPage
    where TViewModel : ViewModelBase
    {
        Page page = CurrentApplication.CreatePage<TPage, TViewModel>();

        if (modal)
        {
            await Navigation.PushModalAsync(page, animated);
        }
        else
        {
            await Navigation.PushAsync(page, animated);
        }
    }

While the implementation could be less verbose, I decided to focus on a simpler way to consume the APIs we created in the ViewModelBase from any implementing ViewModel. As such, usage would appear as follows:

public class MyViewModel : ViewModelBase
{
    public async Task SomethingHappened()
    {
        if (DisplayAlert(“Title”, “Message”, “Accept”, “Cancel”))
        {
            await PushAsync<MyOtherPage, MyOtherViewModel>();
        }
    }
}

In order to use the same Navigation pattern when creating the MainPage of the Forms application, we can add the ability to have our CreatePage method wrap a Page in a NavigationPage and use that directly from our Application class. Here, we’ve also added a simple API surface for defining the construction of the MainPage and separated the instantiation of the MainPage from the App instance’s constructor. The principle of doing that is that the App constructor should be as fast and efficient as possible. Instantiating a Page is not always so, particularly when we add dependency injection and parameter passing later.

public abstract class ApplicationBase : Application
{
    protected abstract Task<Page> CreateMainPage();

    public ApplicationBase Init()
    {
        Page mainPage = null;

        // Magic Voodoo to appease the bootstrapping Gods.
        Task.Run(async () 
            => mainPage = await CreateMainPage()).Wait();

        MainPage = mainPage;

        return this;
    }

    public Page CreatePage<TPage, TViewModel>(bool wrapInNavigationPage = false)
        where TPage : ContentPage
        where TViewModel : ViewModelBase
    {
        var vm = default(TPage);
        var page = default(TViewModel);

        vm.SetWeakPage(page);
        page.BindingContext = vm;

        if (wrapInNavigationPage)
        {
            return new NavigationPage(page);
        }

        return page;
    }
}
public partial class App : ApplicationBase
{
    public App() => InitializeComponent();

    protected override Task<Page> CreateMainPage
        => CreatePage(StartPage, StartViewModel>(true);
}

In order to invoke the CreateMainPage Task, we now do so where we create the App instance: In the iOS AppDelegate class and the Android MainActivity class. It’s not the best solution, but I find it a better alternative than constructing the MainPage in App’s constructor.

var myApp = new App().Init();
LoadApplication(myApp);


Honestly, that was more work than needed to bridge those gaps, but we did so with very little code and little-to-no interference with Xamairn.Forms’ functionality.


You can find the entire project on Github:
https://github.com/jacob-maristany/Xamarin.Forms.Mvvm


Some Notes on Xamarin.Forms Shell:
Shell adds the ability to navigate from anywhere using Routes. It does allow parameter passing during navigation, but since they are embedded in the navigation string, they appear to be limited to basic types and are quite kludgy to use. There also does not seem to be support for alerts from the Shell class. Therefore, a lot of this content still holds true under Shell.


Next Time on…

We’ll fill in some additional gaps by responding to view disappearing and appearing the ViewModel and passing complex types as parameters during navigation.