Xamarin.Forms Quickstart Deep Dive
In the Xamarin.Forms Quickstart, the Notes application was built. This article reviews what has been built to gain an understanding of the fundamentals of how Xamarin.Forms Shell applications work.
Visual Studio organizes code into Solutions and Projects. A solution is a container that can hold one or more projects. A project can be an application, a supporting library, a test application, and more. The Notes application consists of one solution containing three projects, as shown in the following screenshot:
The projects are:
- Notes – This project is the .NET Standard library project that holds all of the shared code and shared UI.
- Notes.Android – This project holds Android-specific code and is the entry point for the Android application.
- Notes.iOS – This project holds iOS-specific code and is the entry point for the iOS application.
The following screenshot shows the contents of the Notes .NET Standard library project in Visual Studio:
The project has a Dependencies node that contains NuGet and SDK nodes:
- NuGet – the Xamarin.Forms, Xamarin.Essentials, Newtonsoft.Json, and sqlite-net-pcl NuGet packages that have been added to the project.
- SDK – the
NETStandard.Library
metapackage that references the complete set of NuGet packages that define .NET Standard.
Visual Studio for Mac follows the Visual Studio practice of organizing code into Solutions and Projects. A solution is a container that can hold one or more projects. A project can be an application, a supporting library, a test application, and more. The Notes application consists of one solution containing three projects, as shown in the following screenshot:
The projects are:
- Notes – This project is the .NET Standard library project that holds all of the shared code and shared UI.
- Notes.Android – This project holds Android-specific code and is the entry point for Android applications.
- Notes.iOS – This project holds iOS specific-code and is the entry point for iOS applications.
The following screenshot shows the contents of the Notes .NET Standard library project in Visual Studio for Mac:
The project has a Dependencies node that contains NuGet and SDK nodes:
- NuGet – the Xamarin.Forms, Xamarin.Essentials, Newtonsoft.Json, and sqlite-net-pcl NuGet packages that have been added to the project.
- SDK – the
NETStandard.Library
metapackage that references the complete set of NuGet packages that define .NET Standard.
The project also consists of multiple files:
- Data\NoteDatabase.cs – This class contains code to create the database, read data from it, write data to it, and delete data from it.
- Models\Note.cs – This class defines a
Note
model whose instances store data about each note in the application. - Views\AboutPage.xaml – The XAML markup for the
AboutPage
class, which defines the UI for the about page. - Views\AboutPage.xaml.cs – The code-behind for the
AboutPage
class, which contains the business logic that is executed when the user interacts with the page. - Views\NotesPage.xaml – The XAML markup for the
NotesPage
class, which defines the UI for the page shown when the application launches. - Views\NotesPage.xaml.cs – The code-behind for the
NotesPage
class, which contains the business logic that is executed when the user interacts with the page. - Views\NoteEntryPage.xaml – The XAML markup for the
NoteEntryPage
class, which defines the UI for the page shown when the user enters a note. - Views\NoteEntryPage.xaml.cs – The code-behind for the
NoteEntryPage
class, which contains the business logic that is executed when the user interacts with the page. - App.xaml – The XAML markup for the
App
class, which defines a resource dictionary for the application. - App.xaml.cs – The code-behind for the
App
class, which is responsible for instantiating the Shell application, and for handling application lifecycle events. - AppShell.xaml – The XAML markup for the
AppShell
class, which defines the visual hierarchy of the application. - AppShell.xaml.cs – The code-behind for the
AppShell
class, which creates a route for theNoteEntryPage
so that it can be navigated to programmatically. - AssemblyInfo.cs – This file contains an application attribute about the project, that is applied at the assembly level.
For more information about the anatomy of a Xamarin.iOS application, see Anatomy of a Xamarin.iOS Application. For more information about the anatomy of a Xamarin.Android application, see Anatomy of a Xamarin.Android Application.
A Xamarin.Forms application is architected in the same way as a traditional cross-platform application. Shared code is typically placed in a .NET Standard library, and platform-specific applications consume the shared code. The following diagram shows an overview of this relationship for the Notes application:
To maximize the reuse of startup code, Xamarin.Forms applications have a single class named App
that is responsible for instantiating the application on each platform, as shown in the following code example:
using Xamarin.Forms;
namespace Notes
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
// ...
}
}
This code sets the MainPage
property of the App
class to the AppShell
object. The AppShell
class defines the visual hierarchy of the application. Shell takes this visual hierarchy and produces the user interface for it. For more information about defining the visual hierarchy of the application, see Application visual hierarchy.
In addition, the AssemblyInfo.cs file contains a single application attribute, that is applied at the assembly level:
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
The XamlCompilation
attribute turns on the XAML compiler, so that XAML is compiled directly into intermediate language. For more information, see XAML Compilation.
How the application is launched on each platform is specific to the platform.
To launch the initial Xamarin.Forms page in iOS, the Notes.iOS project defines the AppDelegate
class that inherits from the FormsApplicationDelegate
class:
namespace Notes.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
}
The FinishedLaunching
override initializes the Xamarin.Forms framework by calling the Init
method. This causes the iOS-specific implementation of Xamarin.Forms to be loaded in the application before the root view controller is set by the call to the LoadApplication
method.
To launch the initial Xamarin.Forms page in Android, the Notes.Android project includes code that creates an Activity
with the MainLauncher
attribute, with the activity inheriting from the FormsAppCompatActivity
class:
namespace Notes.Droid
{
[Activity(Label = "Notes",
Icon = "@mipmap/icon",
Theme = "@style/MainTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
}
The OnCreate
override initializes the Xamarin.Forms framework by calling the Init
method. This causes the Android-specific implementation of Xamarin.Forms to be loaded in the application before the Xamarin.Forms application is loaded.
Xamarin.Forms Shell applications define the visual hierarchy of the application in a class that subclasses the Shell
class. In the Notes application this is the Appshell
class:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Notes.Views"
x:Class="Notes.AppShell">
<TabBar>
<ShellContent Title="Notes"
Icon="icon_feed.png"
ContentTemplate="{DataTemplate views:NotesPage}" />
<ShellContent Title="About"
Icon="icon_about.png"
ContentTemplate="{DataTemplate views:AboutPage}" />
</TabBar>
</Shell>
This XAML consists of two main objects:
TabBar
. TheTabBar
represents the bottom tab bar, and should be used when the navigation pattern for the application uses bottom tabs. TheTabBar
object is a child of theShell
object.ShellContent
, which represents theContentPage
objects for each tab in theTabBar
. EachShellContent
object is a child of theTabBar
object.
These objects don't represent any user interface, but rather the organization of the application's visual hierarchy. Shell will take these objects and produce the navigation user interface for the content. Therefore, the AppShell
class defines two pages that are navigable from bottom tabs. The pages are created on demand, in response to navigation.
For more information about Shell applications, see Xamarin.Forms Shell.
There are several control groups used to create the user interface of a Xamarin.Forms application:
- Pages – Xamarin.Forms pages represent cross-platform mobile application screens. The Notes application uses the
ContentPage
class to display single screens. For more information about pages, see Xamarin.Forms Pages. - Views – Xamarin.Forms views are the controls displayed on the user interface, such as labels, buttons, and text entry boxes. The finished Notes application uses the
CollectionView
,Editor
, andButton
views. For more information about views, see Xamarin.Forms Views. - Layouts – Xamarin.Forms layouts are containers used to compose views into logical structures. The Notes application uses the
StackLayout
class to arrange views in a vertical stack, and theGrid
class to arrange buttons horizontally. For more information about layouts, see Xamarin.Forms Layouts.
At runtime, each control will be mapped to its native equivalent, which is what will be rendered.
The Notes application uses the StackLayout
to simplify cross-platform application development by automatically arranging views on the screen regardless of the screen size. Each child element is positioned one after the other, either horizontally or vertically in the order they were added. How much space the StackLayout
will use depends on how the HorizontalOptions
and VerticalOptions
properties are set, but by default the StackLayout
will try to use the entire screen.
The following XAML code shows an example of using a StackLayout
to layout the NoteEntryPage
:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.Views.NoteEntryPage"
Title="Note Entry">
...
<StackLayout Margin="{StaticResource PageMargin}">
<Editor Placeholder="Enter your note"
Text="{Binding Text}"
HeightRequest="100" />
<Grid>
...
</Grid>
</StackLayout>
</ContentPage>
By default the StackLayout
assumes a vertical orientation. However, it can be changed to a horizontal orientation by setting the StackLayout.Orientation
property to the StackOrientation.Horizontal
enumeration member.
Notă
The size of views can be set through the HeightRequest
and WidthRequest
properties.
For more information about the StackLayout
class, see Xamarin.Forms StackLayout.
An object defined in XAML can fire an event that is handled in the code-behind file. The following code example shows the OnSaveButtonClicked
method in the code-behind for the NoteEntryPage
class, which is executed in response to the Clicked
event firing on the Save button.
async void OnSaveButtonClicked(object sender, EventArgs e)
{
var note = (Note)BindingContext;
note.Date = DateTime.UtcNow;
if (!string.IsNullOrWhiteSpace(note.Text))
{
await App.Database.SaveNoteAsync(note);
}
await Shell.Current.GoToAsync("..");
}
The OnSaveButtonClicked
method saves the note in the database, and navigates back to the previous page. For more information about navigation, see Navigation.
Notă
The code-behind file for a XAML class can access an object defined in XAML using the name assigned to it with the x:Name
attribute. The value assigned to this attribute has the same rules as C# variables, in that it must begin with a letter or underscore and contain no embedded spaces.
The wiring of the save button to the OnSaveButtonClicked
method occurs in the XAML markup for the NoteEntryPage
class:
<Button Text="Save"
Clicked="OnSaveButtonClicked" />
The CollectionView
is responsible for displaying a collection of items in a list. By default, list items are displayed vertically and each item is displayed in a single row.
The following code example shows the CollectionView
from the NotesPage
:
<CollectionView x:Name="collectionView"
Margin="{StaticResource PageMargin}"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Text}"
FontSize="Medium" />
<Label Text="{Binding Date}"
TextColor="{StaticResource TertiaryColor}"
FontSize="Small" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The layout of each row in the CollectionView
is defined within the CollectionView.ItemTemplate
element, and uses data binding to display any notes that are retrieved by the application. The CollectionView.ItemsSource
property is set to the data source, in NotesPage.xaml.cs:
protected override async void OnAppearing()
{
base.OnAppearing();
collectionView.ItemsSource = await App.Database.GetNotesAsync();
}
This code populates the CollectionView
with any notes stored in the database, and is executed when the page appears.
When an item is selected in the CollectionView
, the SelectionChanged
event fires. An event handler, named OnSelectionChanged
, is executed when the event fires:
async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection != null)
{
// ...
}
}
The SelectionChanged
event can access the object that was associated with the item through the e.CurrentSelection
property.
For more information about the CollectionView
class, see Xamarin.Forms CollectionView.
Navigation is performed in a Shell application by specifying a URI to navigate to. Navigation URIs have three components:
- A route, which defines the path to content that exists as part of the Shell visual hierarchy.
- A page. Pages that don't exist in the Shell visual hierarchy can be pushed onto the navigation stack from anywhere within a Shell application. For example, the
NoteEntryPage
isn't defined in the Shell visual hierarchy, but can be pushed onto the navigation stack as required. - One or more query parameters. Query parameters are parameters that can be passed to the destination page while navigating.
A navigation URI doesn't have to include all three components, but when it does the structure is: //route/page?queryParameters
Notă
Routes can be defined on elements in the Shell visual hierarchy via the Route
property. However, if the Route
property isn't set, such as in the Notes application, a route is generated at runtime.
For more information about Shell navigation, see Xamarin.Forms Shell navigation.
To navigate to a page that doesn't exist in the Shell visual hierarchy requires it to first be registered with the Shell routing system. using the Routing.RegisterRoute
method. In the Notes application, this occurs in the AppShell
constructor:
public partial class AppShell : Shell
{
public AppShell()
{
// ...
Routing.RegisterRoute(nameof(NoteEntryPage), typeof(NoteEntryPage));
}
}
In this example, a route named NoteEntryPage
is registered against the NoteEntryPage
type. This page can then be navigated to using URI-based navigation, from anywhere in the application.
Navigation is performed by the GoToAsync
method, which accepts an argument that represents the route to navigate to:
await Shell.Current.GoToAsync("NoteEntryPage");
In this example, the NoteEntryPage
is navigated to.
Important
A navigation stack is created when a page that's not in the Shell visual hierarchy is navigated to.
When navigating to a page, data can be passed to the page as a query parameter:
async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection != null)
{
// Navigate to the NoteEntryPage, passing the ID as a query parameter.
Note note = (Note)e.CurrentSelection.FirstOrDefault();
await Shell.Current.GoToAsync($"{nameof(NoteEntryPage)}?{nameof(NoteEntryPage.ItemId)}={note.ID.ToString()}");
}
}
This example retrieves the currently selected item in the CollectionView
and navigates to the NoteEntryPage
, with the value of ID
property of the Note
object being passed as a query parameter to the NoteEntryPage.ItemId
property.
To receive the passed data, the NoteEntryPage
class is decorated with the QueryPropertyAttribute
[QueryProperty(nameof(ItemId), nameof(ItemId))]
public partial class NoteEntryPage : ContentPage
{
public string ItemId
{
set
{
LoadNote(value);
}
}
// ...
}
The first argument for the QueryPropertyAttribute
specifies that the ItemId
property will receive the passed data, with the second argument specifying the query parameter id. Therefore, the QueryPropertyAttribute
in the above example specifies that the ItemId
property will receive the data passed in the ItemId
query parameter from the URI in the GoToAsync
method call. The ItemId
property then calls the LoadNote
method to retrieve the note from the device.
Backwards navigation is performed by specifying ".." as the argument to the GoToAsync
method:
await Shell.Current.GoToAsync("..");
For more information about backwards navigation, see Backwards navigation.
Data binding is used to simplify how a Xamarin.Forms application displays and interacts with its data. It establishes a connection between the user interface and the underlying application. The BindableObject
class contains much of the infrastructure to support data binding.
Data binding connects two objects, called the source and the target. The source object provides the data. The target object will consume (and often display) data from the source object. For example, an Editor
(target object) will commonly bind its Text
property to a public string
property in a source object. The following diagram illustrates the binding relationship:
The main benefit of data binding is that you no longer have to worry about synchronizing data between your views and data source. Changes in the source object are automatically pushed to the target object behind-the-scenes by the binding framework, and changes in the target object can be optionally pushed back to the source object.
Establishing data binding is a two-step process:
- The
BindingContext
property of the target object must be set to the source. - A binding must be established between the target and the source. In XAML, this is achieved by using the
Binding
markup extension.
In the Notes application, the binding target is the Editor
that displays a note, while the Note
instance set as the BindingContext
of NoteEntryPage
is the binding source. Initially, the BindingContext
of the NoteEntryPage
is set when the page constructor executes:
public NoteEntryPage()
{
// ...
BindingContext = new Note();
}
In this example, the page's BindingContext
is set to a new Note
when the NoteEntryPage
is created. This handles the scenario of adding a new note to the application.
In addition, the page's BindingContext
can also be set when navigation to the NoteEntryPage
occurs, provided that an existing note was selected on the NotesPage
:
[QueryProperty(nameof(ItemId), nameof(ItemId))]
public partial class NoteEntryPage : ContentPage
{
public string ItemId
{
set
{
LoadNote(value);
}
async void LoadNote(string itemId)
{
try
{
int id = Convert.ToInt32(itemId);
// Retrieve the note and set it as the BindingContext of the page.
Note note = await App.Database.GetNoteAsync(id);
BindingContext = note;
}
catch (Exception)
{
Console.WriteLine("Failed to load note.");
}
}
// ...
}
}
In this example, when page navigation occurs the page's BindingContext
is set to the selected Note
object after it's been retrieved from the database.
Important
While the BindingContext
property of each target object can be individually set, this isn’t necessary. BindingContext
is a special property that’s inherited by all its children. Therefore, when the BindingContext
on the ContentPage
is set to a Note
instance, all of the children of the ContentPage
have the same BindingContext
, and can bind to public properties of the Note
object.
The Editor
in NoteEntryPage
then binds to the Text
property of the Note
object:
<Editor Placeholder="Enter your note"
Text="{Binding Text}" />
A binding between the Editor.Text
property and the Text
property of the source object is established. Changes made in the Editor
will automatically be propagated to the Note
object. Similarly, if changes are made to the Note.Text
property, the Xamarin.Forms binding engine will also update the contents of the Editor
. This is known as a two-way binding.
For more information about data binding, see Xamarin.Forms Data Binding.
Xamarin.Forms applications often contain multiple visual elements that have an identical appearance. Setting the appearance of each visual element can be repetitive and error prone. Instead, styles can be created that define the appearance, and then applied to the required visual elements.
The Style
class groups a collection of property values into one object that can then be applied to multiple visual element instances. Styles are stored in a ResourceDictionary
, either at the application level, the page level, or the view level. Choosing where to define a Style
impacts where it can be used:
Style
instances defined at the application level can be applied throughout the application.Style
instances defined at the page level can be applied to the page and to its children.Style
instances defined at the view level can be applied to the view and to its children.
Important
Any styles that are used throughout the application are stored in the application's resource dictionary to avoid duplication. However, XAML that's specific to a page shouldn't be included in the application's resource dictionary, as the resources will then be parsed at application startup instead of when required by a page. For more information, see Reduce the application resource dictionary size.
Each Style
instance contains a collection of one or more Setter
objects, with each Setter
having a Property
and a Value
. The Property
is the name of the bindable property of the element the style is applied to, and the Value
is the value that is applied to the property. The following code example shows a style from NoteEntryPage
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Notes.Views.NoteEntryPage"
Title="Note Entry">
<ContentPage.Resources>
<!-- Implicit styles -->
<Style TargetType="{x:Type Editor}">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>
...
</ContentPage.Resources>
...
</ContentPage>
This style is applied to any Editor
instances on the page.
When creating a Style
, the TargetType
property is always required.
Notă
Styling a Xamarin.Forms application is traditionally accomplished by using XAML styles. However, Xamarin.Forms also supports styling visual elements using Cascading Style Sheets (CSS). For more information, see Styling Xamarin.Forms apps using Cascading Style Sheets (CSS).
For more information about XAML styles, see Styling Xamarin.Forms Apps using XAML Styles.
Visual Studio for Mac and Visual Studio both provide many options for testing and deploying an application. Debugging applications is a common part of the application development lifecycle and helps to diagnose code issues. For more information, see Set a Breakpoint, Step Through Code, and Output Information to the Log Window.
Simulators are a good place to start deploying and testing an application, and feature useful functionality for testing applications. However, users will not consume the final application in a simulator, so applications should be tested on real devices early and often. For more information about iOS device provisioning, see Device Provisioning. For more information about Android device provisioning, see Set Up Device for Development.
This deep dive has examined the fundamentals of application development using Xamarin.Forms Shell. Suggested next steps include reading about the following functionality:
- Xamarin.Forms Shell reduces the complexity of mobile application development by providing the fundamental features that most mobile applications require. For more information, see Xamarin.Forms Shell.
- There are several control groups used to create the user interface of a Xamarin.Forms application. For more information, see Controls Reference.
- Data binding is a technique for linking properties of two objects so that changes in one property are automatically reflected in the other property. For more information, see Data Binding.
- Xamarin.Forms provides multiple page navigation experiences, depending upon the page type being used. For more information, see Navigation.
- Styles help to reduce repetitive markup, and allow an applications appearance to be more easily changed. For more information, see Styling Xamarin.Forms Apps.
- Data templates provide the ability to define the presentation of data on supported views. For more information, see Data Templates.
- Effects also allow the native controls on each platform to be customized. Effects are created in platform-specific projects by subclassing the
PlatformEffect
class, and are consumed by attaching them to an appropriate Xamarin.Forms control. For more information, see Effects. - Each page, layout, and view is rendered differently on each platform using a
Renderer
class that in turn creates a native control, arranges it on the screen, and adds the behavior specified in the shared code. Developers can implement their own customRenderer
classes to customize the appearance and/or behavior of a control. For more information, see Custom Renderers. - Shared code can access native functionality through the
DependencyService
class. For more information, see Accessing Native Features with DependencyService.