Chapter 11. Xamarin.Forms
Since the beginning of Xamarin's lifetime as a company, their motto has always been to expose the native APIs on iOS and Android directly to C#. This was a great strategy at the beginning, because applications built with Xamarin.iOS or Xamarin.Android were pretty much indistinguishable from a native Objective-C or Java application. Code sharing was generally limited to non-UI code that 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 solution for those who know C# (and XAML), but also might not want to get into the full details of using the native iOS and Android APIs.
In this chapter, we will cover the following:
- Create "Hello World" in Xamarin.Forms
- Discuss Xamarin.Forms architecture
- Use XAML with Xamarin.Forms
- Cover data binding and MVVM with Xamarin.Forms
Creating Hello World in Xamarin.Forms
To understand how a Xamarin.Forms application is put together, let's begin by creating a simple "Hello World" application.
Open Xamarin Studio and perform the following steps:
- Create a new solution.
- Navigate to the C# | Mobile Apps section.
- Create a new Blank App (Xamarin.Forms Portable) solution.
- Name your solution something appropriate such as
HelloForms
.
Notice the three new projects that were successfully created: HelloForms
, HelloForms.Android
, and HelloForms.iOS
. In Xamarin.Forms applications, the bulk of your code will be shared, and each platform-specific project is just a small amount of code that starts the Xamarin.Forms framework.
Let's examine the minimal parts of a Xamarin.Forms application:
App.cs
in theHelloForms
PCL library. This class holds the startup page of the Xamarin.Forms application. A simple static method,GetMainPage()
, returns the startup page of the application. In the default project template,- A
ContentPage
is created with a single label that will be rendered as aUILabel
on iOS and aTextView
on Android. MainActivity.cs
in theHelloForms.Android
Android project. This is the main launcher activity of the Android application. The important part for Xamarin.Forms here is the call toForms.Init(this, bundle)
that initializes the Android-specific portion of the Xamarin.Forms framework. Next is a call toSetPage(App.GetMainPage())
that displays the native version of the main Xamarin.Forms page.AppDelegate.cs
in theHelloForms.iOS
iOS project. This is very similar to Android, except iOS applications startup via aUIApplicationDelegate
class.Forms.Init()
will initialize the iOS-specific parts of Xamarin.Forms, whileApp.GetMainPage().CreateViewController()
will generate a native controller that can be used as theRootViewController
of the main window of the application.
Go ahead and run the iOS project; you should see something similar to the following screenshot:
If you run the Android project, you will get a UI very similar to the iOS, but using the native Android controls, as shown in the following screenshot:
Tip
Even though not covered in this module, Xamarin.Forms also supports Windows Phone applications. However, a PC running Windows and Visual Studio is required to develop for Windows Phone. If you can get a Xamarin.Forms application working on iOS and Android, then getting a Windows Phone version working should be a piece of cake.
Understanding the architecture behind Xamarin.Forms
Getting started with Xamarin.Forms is very easy, but it is always good to look behind the curtain to understand what is happening behind the scenes. In the earlier chapters of this module, we created a cross-platform application using native iOS and Android APIs directly. Certain applications are much more suited for this development approach, so understanding the difference between a Xamarin.Forms application and a plain Xamarin application is important to know when choosing what framework is best suited for your app.
Xamarin.Forms is an abstraction over the native iOS and Android APIs that you can call directly from C#. So, Xamarin.Forms is using the same APIs you would in a plain Xamarin application, while providing a framework that allows you to define your UIs in a cross-platform way. An abstraction layer such as this is in many ways a very good thing because it gives you the benefit of sharing the code driving your UI as well as any backend C# code that could have also been shared in a standard Xamarin app. The main disadvantage, however, is a slight hit in performance and being limited by the Xamarin.Forms framework as far as what types of controls are available. Xamarin.Forms also gives the option of writing renderers that allow you to override your UI in a platform-specific way. However, in my opinion, renderers are still somewhat limited in what can be achieved.
See the difference in a Xamarin.Forms application and a traditional Xamarin app in the following diagram:
In both the applications, the business logic and backend code of the application can be shared, but Xamarin.Forms gives you an enormous benefit by allowing your UI code to be shared as well.
Additionally, Xamarin.Forms applications have two project templates to choose from, so let's cover each option:
- Xamarin.Forms Shared: This creates a shared project with all of your Xamarin.Forms code, an iOS project, and an Android project.
- Xamarin.Forms Portable: This creates a portable class library that contains all the shared Xamarin.Forms code, an iOS project, and an Android project.
In general, both the options will work fine for any application. Shared projects are basically a collection of code files that get added automatically to another project referencing it. Using a shared project allows you to use preprocessor statements to implement platform-specific code. Portable class library projects, on the other hand, create a portable .NET assembly that can be used on iOS, Android, and various other platforms. PCLs can't use preprocessor statements, so you generally set up platform-specific code with an interface or abstract/base classes. In most cases, I think a portable class library is a better option since it inherently encourages better programming practices. You can refer to Chapter 3, Code Sharing between iOS and Android, for details on the advantages and disadvantages of these two code-sharing techniques.
Using XAML in Xamarin.Forms
In addition to defining Xamarin.Forms controls from C# code, Xamarin has provided the tool to develop your UI in Extensible Application Markup Language (XAML). 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 web page, with the exception that XAML in Xamarin.Forms creates 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:
- Create a new Xamarin.Forms Portable solution by navigating to C# | Mobile Apps | Blank App (Xamarin.Forms Portable).
- Name the project something appropriate such as
UIDemo
. - Add a new file by navigating to the Forms | Forms ContentPage XAML item template. Name the page
UIDemoPage
. - Open
UIDemoPage.xaml
.
Now, let's edit the XAML code. Add the following XAML code between the <ContentPage.Content>
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="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 screenshot:
Then, on Android, Xamarin.Forms will render the screen in the same way, but with the native Android controls:
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 the iOS status bar. You might be familiar with this syntax for defining rectangles if you are familiar with WPF or Silverlight. Xamarin.Forms uses the same syntax of the 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:
Label
: We used this earlier in the chapter. This is used only to display the text. This maps to aUILabel
on iOS and aTextView
on Android.Button
: This is a general purpose button that can be tapped by a user. This control maps to aUIButton
on iOS and aButton
on Android.Entry
: This control is a single-line text entry. It maps to aUITextField
on iOS and anEditText
on Android.Image
: This is a simple control to display an image on the screen, which maps to aUIImage
on iOS and anImageView
on Android. We used theSource
property of this control that loads an image from theResources
folder on iOS and theResources/drawable
folder on Android. You can also set URLs on this property, but it is best to include the image in your project for the performance.Switch
: This is an on/off switch or a toggle button. It maps to aUISwitch
on iOS and aSwitch
on Android.Stepper
: This is a general-purpose input to enter numbers via two plus and minus buttons. On iOS, this maps to aUIStepper
, while on Android Xamarin.Forms implements this functionality with twoButton
.
This is just some of the controls provided by Xamarin.Forms. There are also more complicated controls such as the ListView
and TableView
you would expect to develop mobile UIs.
Even though we used XAML in this example, you can also implement this Xamarin.Forms page from C#. Here is an example of what this 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 = "xamagon.png", }); layout.Children.Add(new Switch { IsToggled = true, }); layout.Children.Add(new Stepper { Value = 10, }); Content = layout; } }
So you can see that 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.
Using data binding and MVVM
At this point, you should be grasping the basics of Xamarin.Forms, but you may be 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 set up with Xamarin.Forms:
- Your Model and ViewModel layers will remain mostly unchanged from the MVVM pattern we covered earlier in the module.
- Your ViewModel layer should implement the
INotifyPropertyChanged
interface, which facilitates data binding. To simplify things in Xamarin.Forms, you can use theBindableObject
base class and callOnPropertyChanged
when the values change on your ViewModel. - Any page or control in Xamarin.Forms has a
BindingContext
property, which is the object that it is data bound to. In general, you can set a corresponding ViewModel to each view'sBindingContext
property. - In XAML, you can set up data binding using the syntax of the form
Text="{Binding Name}"
. This example will bind the Text property of the control to a Name property of the object residing in theBindingContext
. - In conjunction with data binding, events can be translated to commands using the
ICommand
interface. So, for example, a button's click event can be data bound to a command exposed by a ViewModel. There is a built-inCommand
class in Xamarin.Forms to support this.
Tip
Data binding can also be set up from C# code in Xamarin.Forms via the Binding
class. However, it is generally much easier to set up bindings from XAML, since the syntax has been simplified there.
Now that we have covered the basics, let's go through it step by step and partially convert our XamChat sample application discussed earlier in the module 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 XamChat
:
- First, create three folders in the
XamChat
project namedViews
,ViewModels
, andModels
. - Add the appropriate
ViewModels
andModels
classes from the XamChat application in the earlier chapter. These are found in theXamChat.Core
project. - Build the project and just make sure that everything is saved. You will get a few compiler errors that we will resolve shortly.
The first class that 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>(); private 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 its own IoC container called DependencyService
. It functions very similar to the container we built in the previous chapters, except that it only has one method, Get<T>
, and the registrations are set up via an assembly attribute that we will set up shortly.
Additionally, we removed the IsBusyChanged
event in favor of the INotifyPropertyChanged
interface that supports data binding. Inheriting from BindableObject
gives us the helper method, OnPropertyChanged
, which we use to inform bindings that the value has changed in Xamarin.Forms. Notice that we didn't pass string
, which contains the property name, to OnPropertyChanged
. This method uses 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 set up our required services with DependencyService
. Open App.cs
in the root of the PCL project, and add the following two lines above the namespace declaration:
[assembly: Dependency(typeof(XamChat.Core.FakeWebService))] [assembly: Dependency(typeof(XamChat.Core.FakeSettings))]
DependencyService
will automatically pick up these attributes and inspect the types that we declared. Any interfaces that 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 so that 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 to data bind a button's command. One last change in the View Model layer is to set up INotifyPropertyChanged
for the MessageViewModel
:
Conversation[] conversations; public Conversation[] Conversations {get {return conversations; } set {conversations = value; OnPropertyChanged();} }
Likewise, you can repeat this pattern for the remaining public properties throughout the ViewModel layer, but this is all that we will need for this example. Next, let's create a new Foms ContentPage Xaml
item 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 = "XamChat"; 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 set up 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 content page'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 set up the basics of two text fields, a button, and a spinner complete with all the bindings to make everything work. Since we set up the BindingContext
from the LoginPage
code behind, all the properties are bound to the LoginViewModel
.
Next, create ConversationsPage
as a XAML page as we did earlier, 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 (); Appearing += async (sender, e) => { 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 Appearing
event 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 ListView
to data bind a list of items and display on the screen. We defined a DataTemplate
class that represents a set of cells for each item in the list that 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.cs
file and modify the startup page:
public static Page GetMainPage() { return new NavigationPage(new LoginPage()); }
We used 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 an Android application that can login and view a list of conversations:
Summary
In this chapter, we covered the basics of Xamarin.Forms and learned how it can be very useful to build 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 to declare our Xamarin.Forms UIs and understood how Xamarin.Forms controls are rendered on each platform. We also dived into the concepts of data binding and discovered how to use the MVVM design pattern with Xamarin.Forms. Last but not least, we began porting the XamChat application that was discussed earlier in the module to Xamarin.Forms and we were able to reuse most of the backend code.