Do Uno Mvvm?

Last week was a huge week for the Uno platform with their inaugural Uno conference, #UnoConf. As the technology continues to mature, I’ve no doubt that Uno will become a viable solution for building applications to target all sorts of markets. This includes support being progressively added by the various Mvvm frameworks.

Following my previous posts (MVVM Navigation with Xamarin.Forms Shell and MVVM Navigation with Xamarin.Forms Shell – Part II) where I discussed a simple approach to Mvvm with Xamarin.Forms, I figured I’d so something similar with Uno.

Mvvm with Uno

Let’s get on with it and start with a new Uno project – Download and install the Uno Platform Solution Templates extension from the Visual Studio marketplace, if you haven’t already. In Visual Studio, create a new project based on the Cross-Platform App (Uno Platform) project template. I’m going to call the app DoUnoMvvm.

Creating a Class Library

We’re going to separate out our viewmodels and services into a separate library, so add a new project, DoUnoMvvm.Core, based on the Class Library (.NET Standard) project template. Delete the class1.cs and then add a reference to the class library to each of the head projects (i.e. Droid, iOS, UWAP and Wams).

Adjusting NuGet Package References

Right-click on the solution node in the Solution Explorer window and select Manage NuGet Packages for Solution. Go to the Updates tab, check the Include prerelease option and then check the box alongside the packages Uno.Wasm.Bootstrap, Uno.UI, Microsoft.NETCore.UniversalWindowsPlatform and Uno.Core (don’t check either the Logging packages). Click Update to get the latest version of the packages that are checked.

From the Browse tab on the NuGet-Solution window used in the previous step, enter BuildIt.General.Uno into the search box. Select BuildIt.General.Uno and install the packages into all five of the projects.

Mvvm Basics with ViewModelLocator

Now we should be ready to start writing some code. We’re going to keep it simple with the following steps:

  • Create ViewModelLocator class – used for serving up viewmodels and creating services as required
  • Create an instance of ViewModelLocator in App Resources, making it accessible as a static resource in XAML
  • Create MainViewModel class – the viewmodel for the existing MainPage
  • Update ViewModelLocator with a property Main that returns an instance of the MainViewModel class
  • Set the DataContext of the MainPage to use the Main property on the ViewModelLocator
  • Run the application and show data is being served by the MainViewModel.

Here we go…. firstly a new ViewModelLocator class, which is added to the DoUnoMvvm.Core project

public class ViewModelLocator
{
}

Update App.xaml to create an instance of the ViewModelLocator class

<Application
    x:Class="DoUnoMvvm.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:core="using:DoUnoMvvm.Core"
    RequestedTheme="Light">
  <Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
  </Application.Resources>
</Application>

Now to create the MainViewModel, also in the DoUnoMvvm.Core project. We’ll create a property, WelcomeText, that will return some data to be displayed on MainPage.

public class MainViewModel
{
    public string WelcomeText => "How well do Uno Mvvm?";
}

We need to update the ViewModelLocator class to include the Main property

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
}

And use this property when setting the DataContext for MainPage. I’ve also updated the TextBlock to be data bound to the WelcomeText property.

<Page
    x:Class="DoUnoMvvm.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}"
    mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
  </Grid>
</Page>

Run the application and there we have it, our first data bound page

Quick Navigation using Event Mapping

That’s pretty much the basics of Mvvm. However, following my previous posts discussing navigation, I just want to demonstrate how to abstract navigation away from both the page and the viewmodel – this allows for more independent testing of viewmodels as there’s no interdependency between viewmodels. Here’s the basic process:

  • Add a new page, SecondPage, that we’re going to navigate to
  • Add a corresponding viewmodel, SecondViewModel, and property, Second, on the ViewModelLocator
  • Update SecondPage to set the DataContext to be bound to the Second property on the ViewModelLocator
  • Add a Button to MainPage that invokes a method, Next, on the MainViewModel
  • Add an event, Complete, to MainViewModel, and raise it from the Next method.
  • Add a mapping to the App.xaml.cs that navigates to SecondPage when the Complete method is raised.

And here’s the code. I’m not going to show you the initial SecondPage as it’s just generated from the template and you’ll see it later anyhow. Instead, we’ll jump to the SecondViewModel (if you’re following along you still need to add the SecondPage to the DoUnoMvvm.Shared project in the Pages folder).

public class SecondViewModel
{
    public string ProgressText => "Now you know how to navigate....";
}

Add the Second property to the ViewModelLocator

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
    public SecondViewModel Second => new SecondViewModel();
}

Now back to the SecondPage and I have set the DataContext and bound the TextBlock.

<Page
    x:Class="DoUnoMvvm.Shared.Pages.SecondPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm.Shared.Pages"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Second, Source={StaticResource ViewModelLocator}}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid>
    <TextBlock Text="{Binding ProgressText}" />
  </Grid>
</Page>

Now a Button to invoke the transition from MainPage to SecondPage

<Page
    x:Class="DoUnoMvvm.MainPage" ...
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}" >
  <StackPanel>
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
    <Button Content="Go to Second Page" Click="GoNextClick" />
  </StackPanel>
</Page>

Here we’re simply using a code behind but you could easily use a command. Unfortunately x:Bind doesn’t appear to be working with Uno yet, so you can’t simply bind the Click method to a method on the viewmodel.

public void GoNextClick(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel)?.Next();
}

The Next method simply raises the Complete event

public class MainViewModel
{
    public event EventHandler Complete;

    public string WelcomeText => "How well do Uno Mvvm?";

    public void Next()
    {
        Complete?.Invoke(this, EventArgs.Empty);
    }
}

The final step is to add the mapping to App.xaml.cs to define what happens when the Complete event is triggered on the MainViewModel. Add the following property and method to App.xaml.cs, and update the App class to implement the IApplicationWithMapping interface (which comes from the BuildIt.General.Uno library that you should have added earlier)

public IDictionary<Type, IEventMap[]> Maps { get; } = new Dictionary<Type, IEventMap[]>();
private void MapPageTransitions()
{
    Maps.For<MainViewModel>()
        .Do(new EventHandler((s, args) => (Windows.UI.Xaml.Window.Current.Content as Frame)?.Navigate(typeof(SecondPage))))
        .When((vm, a) => vm.Complete += a, (vm, a) => vm.Complete -= a);
}

Invoke the MapPageTransitions method immediately after the Window.Current.Content property has been set equal to a new Frame. In order for the events to get correctly wired up you also need to update both MainPage and SecondPage to inherit from the MappingBasePage class.

Now when you run the application, MainPage will appear with a Button that you can click to navigate to the SecondPage.

Uno How to Mvvm!

You might be thinking…. you’ve just shown me how to do a bunch of UWP code… and that is EXACTLY the point. If you switch to the Droid or iOS or Wasm target, you can run the same application on each of those platforms with NO further code changes required. The Uno platform is about leveraging the skills you already have as a UWP (or as a Xamarin.Forms) developer, allowing you to build rich, high-quality applications for iOS, Android and Web.

Link to the source code

MVVM Navigation with Xamarin.Forms Shell – Part II

Following my previous post on Mvvm Navigation with Xmarin.Forms Shell there were a few things that I felt I hadn’t addressed adequately.

Loading Data on Appearing

The first thing is how to load data when navigating to a page, and perhaps to do something when the user navigates away from the page. Since it’s the responsibility of the viewmodel to provide the data (i.e. via data binding), we have to invoke a method on the corresponding viewmodel when arriving at a page. In Xamarin.Forms the navigation to/from a page invokes the OnAppearing and OnDisappearing methods, which we can use to request that the viewmodel loads data.

The simplest approach is that for each page that needs to load data, the developer can override the OnAppearing method and simply call a method, for example LoadData, on the corresponding viewmodel. Since most pages are likely to have to load some data, this will quickly become a bit of a drag and something we can easily optimise. We’ll introduce an interface, INavigationViewModel, that when implemented by a viewmodel will define methods OnAppearing and OnLeaving. Then in our BasePage (which we introduced in the previous post) we simply need to check to see whether a viewmodel implements the interface before invoking the appropriate method.

public class BasePage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Wire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnAppearing();
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Unwire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnLeaving();
    }
}

Note: If you’re going to be adapting some of this code for your project, you might want to consider making OnAppearing/OnLeaving return a Task that can be awaited.

Passing Parameter

The next point that we need to cover is how to pass a parameter from one page to the next. In our example, we have a list of items and when the user taps on an item the app navigates to the ItemDetailPage to view the details of the item. This requires some information about the selected item to be passed to the ItemDetailPage.

I’ve seen all sorts of mechanisms for passing data between pages across various application platforms. Some platforms only allow a simple query string to be passed as part of navigating between pages, whilst others allow you to pass entire objects. In the case of Xamarin.Forms the default navigation pattern doesn’t provide for a mechanism to pass data. However, because you create the instance of the new page before navigating to it, there’s a perfect opportunity to pass data from the existing page to the new page.

The following code is adapted from the code in the previous post to illustrate wiring up navigation to the ItemDetailPage based on the SelectedItemChanged event on the ItemsViewModel. Note that the selected item, passed into the event handler as the variable args, is set on the Item property of the ItemDetailViewModel.

// When the SelectedItemChanged event is raised on the ItemsViewModel
// navigate to the ItemDetailPage, passing in a new ItemDetailViewModel
// with the selected item
Maps.For<ItemsViewModel>()
    .Do(new EventHandler<Item>(async (s, args) =>
    {
        if (args == null)
        {
            return;
        }
        var page = new ItemDetailPage();
        if (page.BindingContext is ItemDetailViewModel vm)
        {
            vm.Item = args;
        }
        await Navigation.PushAsync(page);
    }))
    .When((vm, a) => vm.SelectedItemChanged += a, (vm, a) => vm.SelectedItemChanged -= a);

Note: Setting a property on the viewmodel will occur before the OnAppearing method is invoked (see previous discussion regarding loading data) which means the viewmodel can make use of whatever data you pass in when loading data. In this case we could have simply passed in the Id of the item we want to display and have the viewmodel load the rest of the data related to that item.

Returning a Parameter

One thing I’ve seen in a number of MVVM frameworks is the ability to navigate to a new page with the expectation that the page will return data at some point in the future. Whilst there are cases where this is convenient (eg prompting the user for some data) this pattern introduces a very heavy dependency between the lifecycle of two pages and their corresponding viewmodels.

An alternative is to assume that each page/viewmodel is independent and that when you arrive at a page any data that is displayed on the page should be refreshed. Of course, if you’ve got a long list of items that the user has scrolled mid-way down and you reload the list, it’s going to return to the top of the list, making the user experience quite nasty. Furthermore, if the list of items is coming from a service, you don’t want to be reloading that data every time the user goes back and forth to a details page.

Most of the above issues can be handled with appropriate caching of data in a service or manager class. If the user navigates to an item, makes changes and saves that item, the cached data in the service would be updated (along with any call required to any backend services). When the user returns to the list of items, the service would simply return the latest cached data. This addresses the latency issue of having to fetch data from a backend service but how do we address the scroll position issue?

One way is to only update the items in the list that have changed (ie catering for add, edit, delete of items). However, having to write this code for every page is again tiresome. In the past, I investigated a possible workaround for this issue when I discussed immutable data – check out posts I, II and III on working with immutable data

Summary

Again, the disclaimer here is that this isn’t a library that you can just drop in and use in your application. However, feel free to take/leave what code you find useful.

Get the latest source code