Creating a shared UI app for Android & iOS with C# and MVVM
A practical MVVM example for Android, iOS with C# and Xamarin
The Model-View-ViewModel (MVVM) Pattern was introduced earlier in 2005 as a way of developing WPF desktop applications (Windows Presentation Foundation). However, most of its key concepts remain usable today as other technologies are including more support for MVVM-like features including Bindings and Command capabilities as they evolve.
Nowadays, a lot of MV-W(whatever) related technologies got some level of MVVM support like KnockoutJS, Angular, KendoUI and other web and non-web technologies and start bringing Bindings as first-class citizens and allow to combine them with Command Pattern and Asynchronous method invocation (AMI) implementations.
A practical MVVM example:
In an overview, MVVM is highly recommended for:
- Scale-out your views as your project grow and stay organized.
- Separation of concerns between designing UI and focus on logic separately.
- Allowing Testability in your application (mocking, Test Driven Development, Behavior Driven Development and beyond).
Let’s see how you can take advantage of MVVM concepts and re-use those practices to develop a cross-platform mobile application and take advantage of Commands, Bindings and more.
The View
The view might contain two important elements:
- Bindings (which is the glue to make the View work with our ViewModel)
- Commands (abstracts the main actions of the views, including clicks, taps, gestures and more)
- States (portions of the view capable of reacting depending on the state of the ViewModel)
Here is the code for the shared UI between iOS and Android
<local:CardView HeightRequest="70">
<StackLayout Orientation="Horizontal">
<Image Aspect="Fill" HeightRequest="30" WidthRequest="70">
<Image.Source>
<UriImageSource Uri="{Binding FlagSource}"
CacheValidity="3"
CachingEnabled="true"/>
</Image.Source>
</Image>
<StackLayout Orientation="Vertical"
Margin="10,0,10,0"
VerticalOptions="FillAndExpand">
<Label Text="{Binding Model.Name}" LineBreakMode="TailTruncation"
Margin="0" FontSize="20"
TextColor="Black" />
<Label Text="{Binding Model.Region}"
Margin="0"
TextColor="#C0C0C0" />
</StackLayout>
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.iOS>15,10,0,10</OnPlatform.iOS>
<OnPlatform.Android>5,5,5,5</OnPlatform.Android>
</OnPlatform>
</StackLayout.Margin>
</StackLayout>
<local:CardView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BrowseCommand}"
CommandParameter="{Binding}"
/>
</local:CardView.GestureRecognizers>
</local:CardView>
Layout is shared between iOS and Android and rendered native on each platform. Shared functionality includes abstracted controls, layout containers, Commands, Bindings with minimum platform specifics and native control embedding, which are also supported if prefered to use AXAML or XIBs implementations.
The ViewModel
The ViewModel responsibility is to keep properties and data to be served to the view without worrying about how it looks. As mentioned before, it can keep the state of the view and handle observable actions and elements
There are two important elements here that allow decoupling your ViewModel and handle the UI in sync:
- ObservableCollection
- A generic class that allows our view to be observed and keep in sync with our ViewModel - PropertyChanged notifications - Specific notifications that can be attached to the properties to keep Two-Way communication between the View and ViewModel)
Let’s see a ViewModel class for a Search operation and focus on the results.
public class CountrySearchViewModel : BindableObject
{
public CountrySearchViewModel(IEnumerable<CountryViewModel> countries)
{
Results = new ObservableCollection<CountryViewModel>();
Countries = countries;
OnFilter();
}
public IEnumerable<CountryViewModel> Countries { get; }
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
OnPropertyChanged(nameof(SearchText));
OnFilter();
}
}
public ObservableCollection<CountryViewModel> Results { get; private set; }
private void OnFilter()
{
Results.Clear();
var query = string.IsNullOrEmpty(SearchText) ?
Countries.ToArray()
: Countries.Where(item => item.Matches(SearchText)).ToArray();
foreach (var item in query)
{
Results.Add(item);
}
}
}
The ViewModel allows to keep UI in sync with observable collections and properties hooked into the controls. Furthermore, you can take some of the recently introduced C# 6 features.
ViewModel interaction in each platform (side-by-side) built with Commands, Bindings, and Navigation.
The Model
Then the Model class can act like the DTO of your application and come as needed, mean a Database, Web Service, a file, etc with domain-specific logic when required.
public class Country
{
public string Name { get; set; }
public string Region { get; set; }
public string SubRegion { get; set; }
public int Population { get; set; }
public string NativeName { get; set; }
public string Alpha2Code { get; set; }
public string Demonym { get; set; }
}
Handling Platform abstractions & specifics
By having this separation of concerns, other important can be abstracted and adapted into the app to provide common functionalities for the application including:
- Screen-to-Screen Navigation (which can be abstracted to have ViewModel-to-ViewModel navigation through the application).
- Third-party communications (Authentication, Local Storage, WebServices, etc).
- Device capabilities & features (Bluetooth, Camera, Accelerometer, etc).
- Asynchronous & background processing.
Commands, Bindings and ViewModel communication:
<ToolbarItem Command="{Binding SearchCommand, Source={x:Reference RootPage}}"
Order="Secondary"
Text="Search" />
By declaring the Bindings they can be filled with the Commands declared in the ViewModel and arrange the navigation as the following:
SearchCommand = new Command(async obj => await CallSearchAsync());
//... later in the code
private async Task CallSearchAsync()
{
var searchPage = new SearchPage(Countries);
await NavigationService.PushAsync(searchPage);
}
MVVM Highlights & considerations.
- Understand MVVM is a key, and a responsibility to carry-on. You still need to consider if MVVM might be good for your application and take advantage of it when necessary and prevent you from overkilling your apps in case you are building a slightly simple application.
- Reuse the concepts you may already know. Taking ahead of MVVM and can allow you focus on the architecture of your app as you to understand the basics of the underlying technology (desktop, the web, mobile) and platform specifics.
- Adopting an MVVM Framework? Keep in mind: Frameworks are optional. Use them as needed since you might start with a pure MVVM implementation and eventually bring an MVVM toolkit but only for solving a MVVM problem in your app (lots of Views, ViewModels, ViewModel communication, navigation, etc) and flavors vary between different implementations including MvvmLight, MvvmCross, Prism as some of them are adding Reactive programming features like ReactiveUI.
- Consider starting MVVM “pure” and your UI/project becomes wide and complex. An MVVM solution/framework/library and avoid to overwhelm your app with stuff that you might not need.
- Bindings are spreading out. Take them seriously. Before coding your own screens and UI interactions, you might consider avoiding re-inventing the wheel in the platform as some of those concepts are been introduced to both Android and iOS. The recent addition of Android’s Data-Binding Library and ReactiveCocoa for iOS, which might be good options to consider before starting to code.