Since the beginning of Xamarin's lifetime as a company, their motto has always been to present the native APIs on iOS and Android idiomatically to C#. This was a great strategy in the beginning, because applications built with Xamarin.iOS or Xamarin.Android were pretty much indistinguishable from a native Objective-C or Java applications. Code sharing was generally limited to non-UI code, which left a potential gap to fill in the Xamarin ecosystem—a cross-platform UI abstraction. Xamarin.Forms is the solution to this problem, a cross-platform UI framework that renders native controls on each platform. Xamarin.Forms is a great framework for those that know C# (and XAML), but also may not want to get into the full details of using the native iOS and Android APIs.
In this article by Jonathan Peppers, author of the book Xamarin 4.x Cross-Platform Application Development - Third Edition, we will discuss the following topics:
(For more resources related to this topic, see here.)
In addition to defining Xamarin.Forms controls from C# code, Xamarin has provided the tooling for developing your UI in XAML (Extensible Application Markup Language). XAML is a declarative language that is basically a set of XML elements that map to a certain control in the Xamarin.Forms framework. Using XAML is comparable to what you would think of using HTML to define the UI on a webpage, with the exception that XAML in Xamarin.Forms is creating a C# objects that represent a native UI.
To understand how XAML works in Xamarin.Forms, let's create a new page with lots of UI on it. Return to your HelloForms project from earlier, and open the HelloFormsPage.xaml file. Add the following XAML code between the <ContentPage> tag:
<StackLayout Orientation="Vertical" Padding="10,20,10,10">
<Label Text="My Label" XAlign="Center" />
<Button Text="My Button" />
<Entry Text="My Entry" />
<Image Source="https://www.xamarin.com/content/images/
pages/branding/assets/xamagon.png" />
<Switch IsToggled="true" />
<Stepper Value="10" />
</StackLayout>
Go ahead and run the application on iOS and Android, your application will look something like the following screenshots:
First, we created a StackLayout control, which is a container for other controls. It can layout controls either vertically or horizontally one by one as defined by the Orientation value. We also applied a padding of 10 around the sides and bottom, and 20 from the top to adjust for the iOS status bar. You may be familiar with this syntax for defining rectangles if you are familiar with WPF or Silverlight. Xamarin.Forms uses the same syntax of left, top, right, and bottom values delimited by commas.
We also used several of the built-in Xamarin.Forms controls to see how they work:
This are just some of the controls provided by Xamarin.Forms. There are also more complicated controls such as the ListView and TableView you would expect for delivering mobile UIs.
Even though we used XAML in this example, you could also implement this Xamarin.Forms page from C#. Here is an example of what that would look like:
public class UIDemoPageFromCode : ContentPage
{
public UIDemoPageFromCode()
{
var layout = new StackLayout
{
Orientation = StackOrientation.Vertical,
Padding = new Thickness(10, 20, 10, 10),
};
layout.Children.Add(new Label
{
Text = "My Label",
XAlign = TextAlignment.Center,
});
layout.Children.Add(new Button
{
Text = "My Button",
});
layout.Children.Add(new Image
{
Source = "https://www.xamarin.com/content/images/pages/
branding/assets/xamagon.png",
});
layout.Children.Add(new Switch
{
IsToggled = true,
});
layout.Children.Add(new Stepper
{
Value = 10,
});
Content = layout;
}
}
So you can see where using XAML can be a bit more readable, and is generally a bit better at declaring UIs. However, using C# to define your UIs is still a viable, straightforward approach.
At this point, you should be grasping the basics of Xamarin.Forms, but are wondering how the MVVM design pattern fits into the picture. The MVVM design pattern was originally conceived for its use along with XAML and the powerful data binding features XAML provides, so it is only natural that it is a perfect design pattern to be used with Xamarin.Forms.
Let's cover the basics of how data-binding and MVVM is setup with Xamarin.Forms:
Data binding can also be setup from C# code in Xamarin.Forms via the Binding class. However, it is generally much easier to setup bindings from XAML, since the syntax has been simplified with XAML markup extensions.
Now that we have covered the basics, let's go through step-by-step and to use Xamarin.Forms. For the most part we can reuse most of the Model and ViewModel layers, although we will have to make a few minor changes to support data binding from XAML.
Let's begin by creating a new Xamarin.Forms application backed by a PCL named XamSnap:
The first class we will need to edit is the BaseViewModel class, open it and make the following changes:
public class BaseViewModel : BindableObject
{
protected readonly IWebService service =
DependencyService.Get<IWebService>();
protected readonly ISettings settings =
DependencyService.Get<ISettings>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
OnPropertyChanged();
}
}
}
First of all, we removed the calls to the ServiceContainer class, because Xamarin.Forms provides it's own IoC container called the DependencyService. It has one method, Get<T>, and registrations are setup via an assembly attribute that we will setup shortly.
Additionally we removed the IsBusyChanged event, in favor of the INotifyPropertyChanged interface that supports data binding. Inheriting from BindableObject gave us the helper method, OnPropertyChanged, which we use to inform bindings in Xamarin.Forms that the value has changed. Notice we didn't pass a string containing the property name to OnPropertyChanged. This method is using a lesser-known feature of .NET 4.0 called CallerMemberName, which will automatically fill in the calling property's name at runtime.
Next, let's setup our needed services with the DependencyService. Open App.xaml.cs in the root of the PCL project and add the following two lines above the namespace declaration:
[assembly: Dependency(typeof(XamSnap.FakeWebService))]
[assembly: Dependency(typeof(XamSnap.FakeSettings))]
The DependencyService will automatically pick up these attributes and inspect the types we declared. Any interfaces these types implement will be returned for any future callers of DependencyService.Get<T>. I normally put all Dependency declarations in the App.cs file, just so they are easy to manage and in one place.
Next, let's modify LoginViewModel by adding a new property:
public Command LoginCommand { get; set; }
We'll use this shortly for data binding a button's command. One last change in the view model layer is to setup INotifyPropertyChanged for the MessageViewModel:
Conversation[] conversations;
public Conversation[] Conversations
{
get { return conversations; }
set
{
conversations = value;
OnPropertyChanged();
}
}
Likewise, you could repeat this pattern for the remaining public properties throughout the view model layer, but this is all we will need for this example. Next, let's create a new Forms ContentPage Xaml file under the Views folder named LoginPage. In the code-behind file, LoginPage.xaml.cs, we'll just need to make a few changes:
public partial class LoginPage : ContentPage
{
readonly LoginViewModel loginViewModel = new LoginViewModel();
public LoginPage()
{
Title = "XamSnap";
BindingContext = loginViewModel;
loginViewModel.LoginCommand = new Command(async () =>
{
try
{
await loginViewModel.Login();
await Navigation.PushAsync(new ConversationsPage());
}
catch (Exception exc)
{
await DisplayAlert("Oops!", exc.Message, "Ok");
}
});
InitializeComponent();
}
}
We did a few important things here, including setting the BindingContext to our LoginViewModel. We setup the LoginCommand, which basically invokes the Login method and displays a message if something goes wrong. It also navigates to a new page if successful. We also set the Title, which will show up in the top navigation bar of the application.
Next, open LoginPage.xaml and we'll add the following XAML code inside the ContentPage's content:
<StackLayout Orientation="Vertical" Padding="10,10,10,10">
<Entry
Placeholder="Username" Text="{Binding UserName}" />
<Entry
Placeholder="Password" Text="{Binding Password}"
IsPassword="true" />
<Button
Text="Login" Command="{Binding LoginCommand}" />
<ActivityIndicator
IsVisible="{Binding IsBusy}"
IsRunning="true" />
</StackLayout>
This will setup the basics of two text fields, a button, and a spinner complete with all the bindings to make everything work. Since we setup the BindingContext from the LoginPage code behind, all the properties are bound to the LoginViewModel.
Next, create a ConversationsPage as a XAML page just like before, and edit the ConversationsPage.xaml.cs code behind:
public partial class ConversationsPage : ContentPage
{
readonly MessageViewModel messageViewModel =
new MessageViewModel();
public ConversationsPage()
{
Title = "Conversations";
BindingContext = messageViewModel;
InitializeComponent();
}
protected async override void OnAppearing()
{
try
{
await messageViewModel.GetConversations();
}
catch (Exception exc)
{
await DisplayAlert("Oops!", exc.Message, "Ok");
}
}
}
In this case, we repeated a lot of the same steps. The exception is that we used the OnAppearing method as a way to load the conversations to display on the screen.
Now let's add the following XAML code to ConversationsPage.xaml:
<ListView ItemsSource="{Binding Conversations}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding UserName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In this example, we used a ListView to data bind a list of items and display on the screen. We defined a DataTemplate class, which represents a set of cells for each item in the list that the ItemsSource is data bound to. In our case, a TextCell displaying the Username is created for each item in the Conversations list.
Last but not least, we must return to the App.xaml.cs file and modify the startup page:
MainPage = new NavigationPage(new LoginPage());
We used a NavigationPage here so that Xamarin.Forms can push and pop between different pages. This uses a UINavigationController on iOS, so you can see how the native APIs are being used on each platform.
At this point, if you compile and run the application, you will get a functional iOS and Android application that can login and view a list of conversations:
In this article we covered the basics of Xamarin.Forms and how it can be very useful for building your own cross-platform applications. Xamarin.Forms shines for certain types of apps, but can be limiting if you need to write more complicated UIs or take advantage of native drawing APIs. We discovered how to use XAML for declaring our Xamarin.Forms UIs and understood how Xamarin.Forms controls are rendered on each platform. We also dove into the concepts of data binding and how to use the MVVM design pattern with Xamarin.Forms.
Further resources on this subject: