Creating a shared UI app for Android & iOS with C# and MVVM

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:

MVVM overview

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

The view might contain two important elements:

Here is the code for the shared UI between iOS and Android


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
<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:

Let’s see a ViewModel class for a Search operation and focus on the results.

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
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 hooked

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.

1
2
3
4
5
6
7
8
9
10
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:

Commands, Bindings and ViewModel communication:

1
2
3
<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:

1
2
3
4
5
6
7
8
9
10
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.

References