AWH Xamarin Flow
An MVVM Setup for Xamarin.Forms
Xamarin.Forms is a powerful toolkit to build cross-platform mobile apps in the .NET ecosystem. It’s fast, stable, and relies on a mature development stack. Unfortunately it’s only a toolkit and not a full-featured framework. This can make it tough to start a new app or to keep an existing app maintainable as there’s no “correct” way to organize your code.
Thankfully using some common .NET packages and techniques, it’s easy to build a simple, extendable, and unit-testable MVVM setup in Xamarin.Forms. AWH Xamarin Flow is the setup we’ve developed at AWH to make it easy to build an app from the ground up and to keep it maintainable as you go.
The Sample App
I’ve created a sample Xamarin.Forms app using the AWH Xamarin Flow setup and have put the code in a public repo so you can follow along with this post. I recommend really digging through the code as I won’t be covering every aspect of the Flow. Instead I’m going to call attention to some key pieces, look at why they are important, and how you may want to adapt the Flow to suit your app’s needs.
Startup and HostBuilder
The heart of the Flow is the
Startup class. Normally in a Xamarin.Forms app, each platform would initialize a new instance of the
App class. On Android this is done in
MainActivity.cs and on iOS this is done in
AppDelegate.cs. In the Flow, we have created an
Initialize method on the static Startup class. This method will create a new
HostBuilder (from the
Microsoft.Extensions.Hosting NuGet package), that will do things like register services and configuration, then return an instance of the
App class. We can then call this method in each platform’s project in place of creating the
App instance directly.
HostBuilder lets us take advantage of features used in other .NET applications like dependency injection. It also takes an
Action parameter which will let each platform-specific project register services that can use native platform APIs. If your app doesn’t need native services, then you can simply remove the parameter from
Initialize and remove the
.ConfigureServices() line which uses that parameter.
Let’s look at two of the main advantages of using
HostBuilder: configuration and dependency injection.
One of the main advantages to using
HostBuilder in the Flow is the ability to use familiar
appsettings.*.json files for configuration. We can load JSON files embedded in the shared project and register them as sources of configuration values. These values can then be injected using an
IConfiguration instance into whatever class will need them (though you will likely want to create a wrapper service to make your values strongly-typed).
// The GetManifestResourceStream functions cannot be executed from within the
Constants.Environment class let’s us change what files are included based on the project’s current build configuration. For instance, in an API-driven app we may have different URLs for the dev, test, UAT, and production APIs. Having each of those as different build configurations will let us build the app so it can pull in the correct settings to connect to the correct API.
Your app may not need configuration files like this, or it may not need environment-specific files. You can customize what files are loaded and even how they are loaded. There are many options available for loading configurations, but we prefer using
appsettings.json as it is the most common pattern.
The other main advantage of the using
HostBuilder in the Flow is that we can use dependency injection. ASP.NET Core popularized a style of constructor injection that should be very familiar to any experienced .NET developer. By using the
ConfigureServices method on the
HostBuilder instance, we can register all of our application’s services, views, and view models so they can receive dependencies and so they can be dependencies. We even register our
App class so it can receive an instance of the
NavigatorService (more on that later).
public static App Initialize(Action<HostBuilderContext, IServiceCollection> configureNativeServices)
One notable thing we do is to set the static
ServiceProvider property of the
App class to the finished collection of registered services. This is useful when we need to dynamically pull pages and view models in the
PageService and in rare cases where we don’t have access to normal constructor injection. We also use that to retrieve the singleton instance of the
App class we registered which will be returned to the platform projects to be loaded on application start.
A note about service lifetimes: normally, we have three registration types to choose from (singleton, scoped, and transient) but since we are not in a request/response environment (like a web application or API), we can’t use a scoped service so only singletons or transients should be registered.
Having a solid dependency injection setup in the AWH Xamarin Flow helps keep our services, view models, and other classes loosely-coupled. This in turn makes unit testing a lot easier since we can more readily mock dependencies. As a bonus, you can include the Xamarin.Essentials.Interfaces package which includes generated interfaces for the device features included in the Xamarin.Essentials package. That package makes it even easier to make your code testable.
View-View Model Relationship
One of the key aspects of any MVVM setup is the relationship between the view and view model. You need to make sure they are paired up correctly and with the correct separation between them. This is typically done using a “View Model Locator” class which maps views to view models. In the AWH Xamarin Flow, we use the
PageService (detailed in the next section) to load pages and find the correct view model. But the service only receives the type of page and not the view model type. In order to keep them linked, we use an interface called
IPageWithViewModel which every page should implement:
public interface IPageWithViewModel
Then each page implements the interface in the code-behind class and set’s what view model it requires:
public partial class ItemDetailPage : ContentPage, IPageWithViewModel
This ensures that we can easily get the correct view model for the page while keeping the view and view model loosely-coupled. We want the view to not directly deal with the view model but to know only what view model it requires. For the view model, it should know nothing about the view where it is used.
Base View Models
If you’re digging through the code, you may notice something strange in the ViewModels folder: there are two base view models. We have separated out
BaseViewModel class contains our implementation of
INotifyPropertyChanged which is necessary for our bindings in XAML to work. The
BasePageViewModel class contains a
NavigatorService instance and two lifecycle methods:
RefreshAsync. The benefit of separating these comes when you have view models that represent smaller visual data objects than a whole page. This could be a view model that represents an item in a
ListView where you still need the
INotifyPropertyChanged goodness but don’t need the
The lifecycle methods are incredibly useful for correctly calling
async methods when the page is loaded (or returned to). You don’t need to use any trickery to call an
async method from the constructor and you should never need to use an
async void method. This is a particularly difficult problem, especially for new Xamarin developers, and these methods are an extremely valuable aspect of the Flow.
Also noteworthy is how we’ve implemented
SetProperty method is the cleanest and simplest way to handle a property with a backing field while still invoking the
PropertyChanged event as needed. This implementation requires the least amount of boilerplate code for any given property on a view model:
private string _title = string.Empty;
PageService mentioned earlier takes the role of the “View Model Locator” in the AWH Xamarin Flow. It’s responsible for getting an instance of a page by type, getting an instance of the page’s view model, and setting the view model as the binding context of the page. It does all of this using the
ServiceProvider property of the
App class so it leans on the dependency injection we set up in
public Page GetPage<PageType>()
Most of the time your app will not (and should not) deal with the
PageService directly and instead will interact with the
NavigatorService relies heavily on the
PageService and acts as a wrapper around a Xamarin.Forms
NavigationPage instance. It provides the methods you may need for basic app navigation like pushing and popping pages to and from the navigation stack, as well as similar methods for modal pages. Depending on your app’s UI, though, the
NavigatorService may not be a perfect fit as is. You may need a different service that serves a similar role but wraps a
TabbedPage or a
FlyoutPage. Of all of the pieces in the AWH Xamarin Flow, this one may require the most changes for any particular app.
The AWH Xamarin Flow makes it easy to build and maintain a Xamarin.Forms app using techniques similar to other .NET applications. There are other frameworks out there, but this setup achieves much of the same functionality while reducing the number of “black-box” libraries you have to handle. By using some standard packages, we have pieced together a solid and extendible framework of our own.
This setup was created by Tommy Elliott, a Software Developer Team Lead at AWH. A huge thank you to him for putting the setup together, helping me create the sample project, and generally helping me get my head around Xamarin. He also coined the name, which is far better than constantly calling it “our setup”.