Mixing MvvmCross Native Views and Xamarin.Forms Pages

If you’ve read my blog before you probably already know that I’m a avid Mvvm supporter. You’ll also know that I use FreshMvvm alot in my Xamarin.Forms solutions. While I love FreshMvvm it’s always important to pick the right tool for the job.

In a recent project at XAM Consulting we had a project to enhance a large pre-existing application that was built in MvvmCross and ‘Xamarin Traditional’. There was a few things to consider on this project a) it was a pre-existing app with a large amount of business logic b) there was a large amount of changes required c) there was also a large amount of existing Native Views.

If you’ve been in software long enough you know rewriting is the wrong thing to do in the majority of cases. The original application had nice clean code and good architecture, we wanted to keep this but when building new screens it’s very hard to look past the advantages of Xamarin.Forms.

So essentially we wanted to both keep the Native Views and add in a few pages of Xamarin.Forms. Currently all the solutions for MvvmCross and Xamarin.Forms are related to having a Forms app with MvvmCross backend, not mixing the two.

I’ve included a diagram below to help you understand what’s happening in the app.

MvvmCrossAndXamarinForms

So what’s the secret sauce? 

It’s important to understand this is really a MvvmCross application but with support Xamarin.Forms pages instead of Native View.

Here’s some of the major components done to implement this, not a step by step guide.

1) Implementing custom MvvmCross presenters

This need to be done in both the iOS project and the Android project. In iOS the class we need to inherit is MvxTouchViewPresenter and we need to override the Show method. In Android the class we need to inherit is MvxAndroidViewPresenter. 

In Android

1
public class MvxFormsDroidPagePresenter : MvxAndroidViewPresenter, IMvxAndroidViewPresenter

In iOS

1
public class MvxFormsTouchPagePresenter : MvxTouchViewPresenter

2) Setup MvvmCross to use the new presenters

To do this we need to override the CreatePresenter method on the Setup.

In Android

1
2
3
4
protected override IMvxAndroidViewPresenter CreateViewPresenter ()
{
    return new MvxFormsDroidPagePresenter ();
}

In iOS 

1
2
3
4
5
protected override IMvxTouchViewPresenter CreatePresenter ()
{
    Forms.Init ();
    return new MvxFormsTouchPagePresenter (ApplicationDelegate ,Window);
}

3) The Secret Sauce

Generally the concepts are the same on both platforms, when we come across a ViewModel ending with XFViewModel then we assume it’s Xamarin.Forms View Model. When it’s Xamarin.Forms ViewModel we 1) Find the Xamarin.Forms Views 2) Convert it into a Native View 3) Setup the BindingContext 4) Display/Native the Native view as if it’s a MvvmCross view.

In iOS 

iOS is was much easier, primarily because Xamarin.Forms allows you to easily create a ViewController.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public override void Show (MvxViewModelRequest request)
{
    // XFViewModel means we need a Forms View
    if (request.ViewModelType.FullName.Contains ("XFViewModel")) 
    {
        //get the xamarin.forms page from the ViewModel
        var contentPage = MvxPresenterHelpers.CreatePage (request); 
 
        //MvvmCross call to create the view model with DI etc
        var viewModel = MvxPresenterHelpers.LoadViewModel (request); 
 
        //Assign it to the Forms Binding Content
        contentPage.BindingContext = viewModel;             
 
        //Creating the view controller from the content page
        var viewController = contentPage.CreateViewController ();
 
        if (this.MasterNavigationController == null) 
        {
            // If it's the first view 
            this.ShowFirstView (viewController);
        } 
        else 
        {
            // If it's already in the stack
            this.MasterNavigationController.PushViewController (viewController, true);
        }
 
    } 
    else 
    {
        //Using a normal MvvmCross View
        base.Show (request);
    }
}

In Android

It turns out this is a bit harder in Android. This is because Xamarin.Forms Navigation and MvvmCross navigation is very different, Xamarin.Forms has a single Activity for a application while MvvmCross has an activity per page, we need to trick the Xamarin.Forms pages into using a single activity.

 

Below is the Xamarin.Forms container activity, normally there would only be a single Activity but in this case we want to create a Activity for every page. You’ll also notice we resolve the MvcAndroidLifetimeMonitor and we pass the Forms activity events, this hooks the Xamarin.Forms page into the MvvmCross Navigation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[Activity (Label = "XamarinForm", Theme = "@android:style/Theme.Holo.Light", NoHistory = false)]
public class MvxFormsApplicationActivity : FormsApplicationActivity
{
    public static Page CurrentPage;
 
    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);
 
        Forms.Init (this, bundle);
 
        SetPage (CurrentPage);
 
        var lifetimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity> () as MvxAndroidLifetimeMonitor;
        lifetimeMonitor.OnCreate (this);
    }
 
    protected override void OnStart ()
    {
        base.OnStart ();
        var lifetimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity> () as MvxAndroidLifetimeMonitor;
        lifetimeMonitor.OnStart (this);
    }
 
    protected override void OnRestart ()
    {
        base.OnRestart ();
        var lifetimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity> () as MvxAndroidLifetimeMonitor;
        lifetimeMonitor.OnRestart (this);
    }
 
    protected override void OnResume ()
    {
        base.OnResume ();
        var lifetimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity> () as MvxAndroidLifetimeMonitor;
        lifetimeMonitor.OnResume (this);
    }
 
    protected override void OnDestroy ()
    {
        base.OnDestroy ();
        var lifetimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity> () as MvxAndroidLifetimeMonitor;
        lifetimeMonitor.OnDestroy (this);
    }
}

 

Now that we have the container for the Xamarin.Forms Page we can override the show method and handle the Xamarin.Forms ViewModel requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public override void Show (MvxViewModelRequest request)
{
    //detect if we want a Forms View 
    if (request.ViewModelType.FullName.Contains ("XFViewModel")) {
 
        //get the Forms page from the request 
        var contentPage = MvxPresenterHelpers.CreatePage (request);
 
        //set DataContext of page to LoadViewModel
        var viewModel = MvxPresenterHelpers.LoadViewModel (request);
 
        //set the binding context of the content page
        contentPage.BindingContext = viewModel;
 
        //set the current page of the activity 
        MvxFormsApplicationActivity.CurrentPage = contentPage;
 
        //Start the Xamarin.Forms Activity
        Activity.StartActivity (typeof(MvxFormsApplicationActivity));
 
    } else {
        base.Show (request);
    }
}

 

Android also requires some extra work when Pages/Views are closed, this is because MvvmCross ignores close events if it’s not a MvvmCross view, which in this case it isn’t. So we need to handle the close ourselves and skip the ignore clauses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public override void ChangePresentation (MvxPresentationHint hint)
{
    if (hint is MvxClosePresentationHint) {
        var viewModel = (hint as MvxClosePresentationHint).ViewModelToClose;
 
        Activity activity = this.Activity;
 
        IMvxView mvxView = activity as IMvxView;
 
        activity.Finish ();
 
        return;
    }                
}

4) The helper methods

You might have noticed both the iOS project and the Android project referenced some helper function, here’s the code from the helper classes. The functions are used to help create the View Models and Pages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static class MvxPresenterHelpers
{
    /// <summary>
    /// Creates a view model from a view model request
    /// </summary>
    public static IMvxViewModel LoadViewModel (MvxViewModelRequest request)
    {
        var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader> ();
        var viewModel = viewModelLoader.LoadViewModel (request, null);
        return viewModel;
    }
 
    /// <summary>
    /// Create a Xamarin.Forms page from a View Model request
    /// </summary>
    public static Page CreatePage (MvxViewModelRequest request)
    {
        var viewModelName = request.ViewModelType.Name;
        var pageName = viewModelName.Replace ("XFViewModel", "Page");
        var pageType = request.ViewModelType.GetTypeInfo ().Assembly.CreatableTypes ()
            .FirstOrDefault (t => t.Name == pageName);
        if (pageType == null) {
            Mvx.Trace ("Page not found for {0}", pageName);
            return null;
        }
 
        var page = Activator.CreateInstance (pageType) as Page;
        if (page == null) {
            Mvx.Error ("Failed to create ContentPage {0}", pageName);
        }
        return page;
    }
}

The End

So that’s the core pieces of the puzzles, this blog is not a step by step guide so if you want a easy way to see this code work I recommend looking at it on github.

Github Sample Repository

Need an App built or Xamarin.Forms training? – You can hire me!

Want to be a Awesome Xamarin.Forms developer? I’m building a eCourse that’ll teach you all that you everything you need to know.

SecretsOfAXamarinFormsNinja

Leave a Reply