In this series I will try to demonstrate the MVVM pattern and how it can be applied to cross platform applications using Windows Runtime and Xamarin for android. Full sample can be downloaded here.
Table of Contents
Xamarin.Forms iOS Application
In this post, following the same approach we took with the iOS application, we want to create a Xamarin.Forms applications that will be able to render the Android and iOS applications using the existing view models created previously.
Xamarin.Forms & MvvmCross
Before starting the implementation it is crucial to understand that neither MvvmCross nor Xamarin.Forms is a replacement for one another. While both of them provide infrastructure modules that help developers in their mobile application projects, Xamarin.Forms is a UI technology stack that enables common XAML declarative UI implementation for target platforms; MvvmCross is a framework that enforces clear distinction between the UI elements and core logic implementation. Either of them alone or together can increase the shared amount of code in cross platform projects.
Let us take a closer look at the interaction of the modules in a Xamarin native project with MvvmCross and a Xamarin.Forms implementation with MvvmCross.
In the native implementation, the MvxApplication, implemented in the Core PCL (Portable Class Library) project is the access point to the application. The core library contains the view model implementation as well as other common service implementation references (e.g. data types, interfaces for IoC etc.). Any time a certain ViewModel is requested by another ViewModel or first view registration, the platform specific presenter will try to find a matching view on the platform specific implementation projects (i.e. IMvxIosViewPresenter and IMvxAndroidViewPresenter).
This selection is executed on premise of naming convention (HubPageViewModel -> HubPageView) or the type parameter that is used while creating the view (GroupsViewModel -> GroupsView : MvxTableViewController)
So now let us add Xamarin.Forms into the mix which takes over the responsibility of rendering the views designed in “common” ground. This time MvxApplication modules would be calling the forms presenter which in return would generate/render the platform specific view on the target platform.
Note that, the diagram is actually showing the setup after we delegate the navigation and view/viewmodel pairing to MVVM Cross. So in the final architecture, mvvm cross is still going to be responsible for finding the matching view to the requested view-model, and Xamarin.Forms would be responsible for dispatching the associated view to the screen on the target platform.
Creating the Xamarin.Forms Application
Since we want to use the existing view-models from the previous projects, we will be adding the Xamarin.Forms application into the existing solution. Selecting the Xamarin.Forms cross-platform application project template with Xamarin.Forms as the UI technology and PCL as the sharing strategy would create 3 new projects (i.e. one for common View and ViewModels, two for platform specific “containers”.
After the projects are created, we can start referencing the Core project from the platform specific projects (i.e. Forms.iOS and Forms.Android) as well as the common view project (i.e. Forms.Common).
The projects would still not build since the MvvmCross dependencies are still missing from the Forms.Common, Forms.iOS and Forms.Android projects.
We should now reference the main nuget package for MvvmCross and its dependencies (MvvmCross, MvvmCross.Core, MvvmCross.Binding, MvvmCross.Platform). After these essential requirements are completed, we need to install the Xamarin.Forms.Presenter package which carries the components for Xamarin.Forms implementation (such as MvxFormsApplication, MvxFormsIosPagePresenter and MvxContentPage…etc.).
The final list of packages for both the Forms.Common and platform specific projects would look similar to the screen shot below.
Now that our project structure is ready, we can start hooking up Xamarin.Forms pages to the view models created previously.
Setting up the iOS Application
First thing first, let us create our MvvmCross ios setup (i.e. MvxIosSetup). The most crucial part of this setup is the CreatePresenter method which will initialize the Xamarin.Forms module and create the MvxFormsIosPagePresenter which will be initializing the Xamarin.Forms pages in response to incoming ViewModel requests.
public class Setup : MvxIosSetup { public Setup(IMvxApplicationDelegate applicationDelegate, UIWindow window) : base(applicationDelegate, window) { } public Setup(IMvxApplicationDelegate applicationDelegate, IMvxIosViewPresenter presenter) : base(applicationDelegate, presenter) { } protected override IMvxApplication CreateApp() { return new Core.App(); } protected override IMvxIosViewPresenter CreatePresenter() { global::Xamarin.Forms.Forms.Init(); var xamarinFormsApp = new MvxFormsApp(); return new MvxFormsIosPagePresenter(Window, xamarinFormsApp); } }
It is also important to note that the constructors were originally (from the previous MvvmCross implementation) using MvxApplicationDelegate concrete class. This was replaced with the interface to accommodate the MvxFormsApplicationDelegate class which we will use as the access point for the iOS platform.
So now let us prepare the application delegate which will take of the core application initialization after the ios application has FinishedLaunching
[Register("AppDelegate")] public partial class AppDelegate : MvxFormsApplicationDelegate { public override UIWindow Window { get; set; } public override bool FinishedLaunching(UIApplication app, NSDictionary options) { Window = new UIWindow(UIScreen.MainScreen.Bounds); var setup = new Setup(this, Window); setup.Initialize(); var startup = Mvx.Resolve<IMvxAppStart>(); startup.Start(); Window.MakeKeyAndVisible(); return true; } }
After this setup is complete, we can start adding the views to the Forms.Common project (i.e. or you can use the existing views from the template application). Once thing to make sure is to use the MvvmCross.Forms.Presenter classes as base class for your view implementations (e.g. instead of ContentPage, we should use MvxContentPage or MvxContentPage for creating a simple content page). The same goes for the XAML declarations.
Running the Application
Once the setup is complete, we can try compiling and running the application. However, the enthusiasm might be immediately killed off with the following exception:
2017-03-26 10:21:21.672 CubiSoft.Samples.Mvvm.Client.Forms.iOS[29860:1309173] mvx: Diagnostic: 0.72 Page not found for HubPagePage
2017-03-26 10:21:21.677 CubiSoft.Samples.Mvvm.Client.Forms.iOS[29860:1309173] mvx: Error: 0.73 Skipping request for HubPageViewModel
2017-03-26 10:21:21.698 CubiSoft.Samples.Mvvm.Client.Forms.iOS[29860:1309173] *** Assertion failure in -[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UIApplication.m:3315
Unhandled Exception:Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Application windows are expected to have a root view controller at the end of application launch
But we did create the view that is associated with the HubPageViewModel view-model, so what is this exception about??!@$
The reason for this is that the Forms presenter expects the view to be implemented in the same project/assembly (where the viewrequest was originated, and looks for a view that is named as HubPagePage (from HubPageViewModel). However in our case we created a completely new project for the Xamarin.Forms application projects.
So the fix for this (if you in fact want to keep the view-models and core implementation separate from the views) would be to manually add an association between the viewmodel and views after the setup is complete, before the application startup is called. So with the additional registration, our AppDelegate.FinishedLaunching override would look like:
public override bool FinishedLaunching(UIApplication app, NSDictionary options) { Window = new UIWindow(UIScreen.MainScreen.Bounds); var setup = new Setup(this, Window); setup.Initialize(); Mvx.Resolve<IMvxViewsContainer>()?.Add(typeof(HubPageViewModel), typeof(HubPagePage)); var startup = Mvx.Resolve<IMvxAppStart>(); startup.Start(); Window.MakeKeyAndVisible(); return true; }
There we have it, reusing the ItemsPage.xaml from the template created changing couple of bindings, we have a functioning Xamarin.Forms + MVVM Cross hybrid.
This of course requires additional work on page types like TabbedPage and setting up Android part of the equation. These will be discussed in the next posts.
Happy coding everyone,