ISE Blog

How to Use Xamarin.Forms to Create a Cross-Platform Mobile App

Xamarin has gained a lot of traction in the mobile development community as a viable cross-platform solution for developing performant mobile apps. When we first started with Xamarin a few years ago, the only option was to create a platform-specific UI (Views and ViewControllers/Presenters) for each platform you wanted to bring your app to. Then Xamarin introduced Forms, a solution for simple mobile apps to be created cross-platform with a shared UI that still used native controls on each platform. As many mobile apps contain as much or more presentation logic as business logic, this makes much more sense as a cross-platform solution.

It is now possible to create a pretty full featured and performant mobile app using Forms. I'll walk you through a simple start to an app which uses multiple views and lists and can run on both platforms. For this example, I'm developing using Xamarin Studio on my Mac.

Set up

  • Open Xamarin Studio and select New Solution... Select Forms App and Next
  • Name it RecipeBook, Android and iOS platforms, Portable Class Library, and XAML for interfaces.
  • Select a location and Create

Xaml Compilation

After your project is created you want to make sure to turn on XAML compilation for the entire assembly. To do this open App.xaml.cs (by expanding the tree next to App.xaml in the explorer) and add the following code right above the namespace definition.

App.xaml.cs
using Xamarin.Forms.Xaml;
 
[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
namespace RecipeBook


XAML compilation is an important step to improve performance of Forms apps.

Create data model

For this sample app just create a simple data model in the App class. For a production app a local persistent data store plus a possible cloud storage solution and API would be used.

App.xaml.cs
using System.Collections.Generic;
//... missing a few lines here
    public partial class App : Application
    {
        public static IDictionary<string, IList<string>> RecipesDictionary { get; set; }
 
        public App ()
        {
            initializeDictionary ();
            InitializeComponent ();
 
            MainPage = new RecipeBookPage ();
        }
 
        private void initializeDictionary ()
        {
            RecipesDictionary = new Dictionary<string, IList<string>> ();
            RecipesDictionary["Breakfast"] = new List<string> {
                "Cereal", "Eggs", "Bacon", "Pancakes", "Cold Pizza"
            };
            RecipesDictionary ["Lunch"] = new List<string> {
                "Cereal", "Salad", "Soup", "Sandwich", "Pizza"
            };
            RecipesDictionary ["Dinner"] = new List<string> {
                "Cereal", "Sushi", "Steak", "Tacos", "Pizza"
            };
        }

 

This dictionary will be used as a starting point for categories and recipes in the app.

Create Categories Page

Replace the default created RecipeBookPage.xaml with the following code.

RecipeBookPage.xaml
<?xml version="1.0" encoding="utf-8"?>
        x:Class="RecipeBook.RecipeBookPage" Title="Categories"
        x:Name="RecipeBookPage">
    <ContentPage.Content>
        <StackLayout VerticalOptions="FillAndExpand"
                     HorizontalOptions="FillAndExpand"
                     Orientation="Vertical"
                     Spacing="15">
            <ListView x:Name="CategoriesView"/>
            <Button x:Name="AddButton" Text="Add Category"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

 

ContentPage

Added the Title="Categories" and the x:Name="RecipeBookPage". Title will used to display a Title in the Navigation/Action bars, and Name is a good habit to be in adding to your View elements so they can be referenced in the code if needed.

StackLayout

A simple layout which organizes subviews in either a horizontal or vertical stack.

"FillAndExpand" set at this level means the StackLayout itself will fill and expand all the available space vertically and horizontally on the ContentPage. 

Spacing="15" puts spacing between subviews.

ListView

This view will display the list of categories to the user. It uses a ListView on Android and a TableView on iOS.

x:Name="CategoriesView" - Set name to be able to reference in code

Button

This button will be used to add a category to the list. It has a name so it can be referenced in the code, and Text to be displayed on the button.

Tie list to data model

In your RecipeBookPage.xaml.cs add the following code.

RecipeBookPage.xaml.cs
using Xamarin.Forms;
using System.Collections.ObjectModel;
 
namespace RecipeBook
{
    public partial class RecipeBookPage : ContentPage
    {
        public ObservableCollection<string> Categories { get; set; }
 
        public RecipeBookPage ()
        {
            InitializeComponent ();
 
            Categories = new ObservableCollection<string> (App.RecipesDictionary.Keys);
            CategoriesView.ItemsSource = Categories;
        }
    }
}

 

ObservableCollection

This is a collection that can be data bound to the ListView so that it is automatically updated whenever the contents change. Create it as a property in the class, and set the Keys of the RecipesDictionary to it. Then set the CategoriesView (the name of the ListView in the XAML file) ItemsSource to it.

Run your app!

You should now be able to run your app on both an Android and an iOS device and see the list of categories and the button displayed. Xamarin Studio will let you use simulators/emulators, as well as physical devices. For Android devices you'll need to enable developer options/USB debugging as usual, and trust the computer. For iOS devices you also need to trust the computer.

App Display 

You'll notice right away that the user interface is not how you'd normally want it - the list extends all the way into the status bar on iOS. It's time to add Navigation!

Wrapping in Navigation

This is something that is somehow as simple/simpler in Xamarin.Forms than it is for either platform natively! All you need to do to add Hierarchical Navigation into your Forms app is make the following change in App.xaml.cs:

App.xaml.cs
public App ()
{
    initializeDictionary ();
    InitializeComponent ();
 
    MainPage = new NavigationPage(new RecipeBookPage ());
}

 

Instead of simply setting MainPage to a new RecipeBookPage as was done by default, create a new NavigationPage and pass in the new RecipeBookPage. This will result in the following display instead:

App Display Categories

Notice also the "Categories" Title we specified for the page is displayed in the navigation bar.

User Interaction

Now that you're able to display the data in a list and it looks halfway decent, allow the user to interact with it.

Add Category Button

Let's just make the Add Category button add another meal. Who says there are only 3 meals in a day? Add the following into your RecipeBookPage.xaml:

RecipeBookPage.xaml
<Button x:Name="AddButton" Text="Add Category" Clicked="Handle_Clicked"/>

 

When you type Clicked=" you might even be prompted by Xamarin Studio to auto-complete with Handle_Clicked and insert the event handler into your cs code. Go with it and you can fill in the details:

RecipeBookPage.xaml.cs
void Handle_Clicked (object sender, System.EventArgs e)
{
    string categoryName = string.Format ("{0}th meal", Categories.Count + 1);
    App.RecipesDictionary [categoryName] = new List<string> ();
 
    Categories = new ObservableCollection<string> (App.RecipesDictionary.Keys);
    CategoriesView.ItemsSource = Categories;

 

This code adds the category to the data model, then updates the Categories to reflect the new state of the data model. If a more complex data model was used that could replace this directly, and an async could be used to get the updated state of the data model to update Categories, without blocking the UI thread. For our purposes this will suffice. Try it out and you'll see pressing the Add Category button will add 4th meal, 5th meal, etc for each press.

Navigating to a new page from the first

First add some event handlers to the ListView to handle the user's interactions with the list:

RecipeBookPage.xaml
<ListView x:Name="CategoriesView" ItemTapped="Handle_ItemTapped" ItemSelected="Handle_ItemSelected"/>

 

ItemTapped="Handle_ItemTapped" - Set event handler name for tapping on list items

ItemSelected="Handle_ItemSelected" - Set item selected event handler function

In your cs file, then add the following:

RecipeBookPage.xaml.cs
async void Handle_ItemTapped (object sender, Xamarin.Forms.ItemTappedEventArgs e)
{
    await Navigation.PushAsync (new RecipesPage (e.Item.ToString ()));
}
 
void Handle_ItemSelected (object sender, Xamarin.Forms.SelectedItemChangedEventArgs e)
{
    ((ListView)sender).SelectedItem = null;
}

 

The Handle_ItemSelected is simply to set the selection back to no selection after a user selects a list item. This is so that when the user navigates back to the Categories page it won't still have the previous selection selected.

Handle_ItemTapped is navigating to a new page which displays the recipes for the selected category. Navigation.PushAsync uses the Navigation we set up in App.xaml.cs and pushes a new page onto the stack. This won't build because you need to create it!

Create the RecipesPage

Right click the RecipeBook project (not the Solution!) in the explorer and select Add > New File... Then select Forms ContentPage Xaml and set RecipesPage to the name.

Recipies Page

You'll now have two new files to update. Replace the code in both:

RecipesPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
        x:Class="RecipeBook.RecipesPage" x:Name="RecipesPage">
    <ContentPage.Content>
        <StackLayout VerticalOptions="FillAndExpand"
                     HorizontalOptions="FillAndExpand"
                     Orientation="Vertical"
                     Spacing="15">
            <ListView x:Name="RecipesView" ItemTapped="Handle_ItemTapped"/>
            <StackLayout VerticalOptions="Fill"
                         HorizontalOptions="FillAndExpand"
                         Orientation="Horizontal"
                         Spacing="10"
                         Padding="5">
                <Entry x:Name="RecipeName" Placeholder="Recipe Name" HorizontalOptions="FillAndExpand"/>
                <Button x:Name="AddButton" Text="Add Recipe" Clicked="Handle_Clicked" HorizontalOptions="End"/>
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

 

RecipesPage.xaml.cs
using System.Collections.ObjectModel;
using Xamarin.Forms;
 
namespace RecipeBook
{
    public partial class RecipesPage : ContentPage
    {
        public ObservableCollection<string> Recipes { get; set; }
 
        public RecipesPage (string category)
        {
            InitializeComponent ();
            Recipes = new ObservableCollection<string> (App.RecipesDictionary [category]);
            this.Title = category;
            RecipesView.ItemsSource = Recipes;
        }
 
        void Handle_Clicked (object sender, System.EventArgs e)
        {
            App.RecipesDictionary [Title].Add (RecipeName.Text);
 
            Recipes = new ObservableCollection<string> (App.RecipesDictionary[Title]);
            RecipesView.ItemsSource = Recipes;
 
            RecipeName.Text = "";
        }
 
        async void Handle_ItemTapped (object sender, Xamarin.Forms.ItemTappedEventArgs e)
        {
            // TODO: View/edit recipe details
        }
    }
}

 

I won't go into as much detail on this page, as most of it is the same as the previous one. Significant differences are the Entry field for allowing users to choose a recipe name to add to the current category. Pay attention to the nested StackLayout and the HorizontalOptions for each of the views in it to achieve the desired layout. Finally, note that the RecipeName.Text is set back to an empty string after the user's entry is added. Also significant is that the category is passed into the page constructor - this is one way to pass data between pages.

Running the app now you'll see the user is able to add categories and add recipes to each category, viewing the list for each category. If you look closely you may notice a few things that aren't quite right for each platform that can be fixed though.

Platform optimizations

The Forms app works on both iOS and Android, but to ensure a better user experience optimizations are necessary.

Android cell text

Depending on your Android device and version, the default text color may not be set properly and you could end up with variable results, sometimes even with empty cells showing in the list. To correct this set the text color of your cells by changing your ListView xaml to the following:

RecipeBookPage.xaml
<ListView x:Name="CategoriesView" ItemTapped="Handle_ItemTapped" ItemSelected="Handle_ItemSelected">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding .}" TextColor="Black" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
RecipesPage.xaml
<ListView x:Name="RecipesView" ItemTapped="Handle_ItemTapped">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding .}" TextColor="Black" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

 

This allows you to specify how the list cells are displayed and populated in greater detail. In this case we'll use a simple TextCell and we'll use 'Binding .' for Text to indicate that the item source itself, rather than any property of it should be used. The important part here is TextColor="Black" which makes sure text is black across platforms, versions and devices. Note this change is actually applying to both platforms, but is only needed because of the differences between Android devices and versions.

iOS Keyboard Overlap

You'll notice that when you select the textfield on the bottom of the RecipesPage on iOS the keyboard displays and covers the textfield, rather than moving it up to see what you're typing. This is an issue in Xamarin.Forms right now, but there is a community created Nuget package (MIT license) which fixes it.

https://github.com/paulpatarinski/Xamarin.Forms.Plugins/tree/master/KeyboardOverlap

To add this package expand your iOS project (RecipeBook.iOS) right click Packages > Add Package... Then search and select the KeyboardOverlap package as seen below.

KeyboardOverlap Package

Then open your AppDelegate.cs file in your iOS project and add the following in:

AppDelegate.cs
using Foundation;
using KeyboardOverlap.Forms.Plugin.iOSUnified;
using UIKit;
//... skipping a few lines
 
        public override bool FinishedLaunching (UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init ();
            KeyboardOverlapRenderer.Init ();
 
            LoadApplication (new App ());
 
            return base.FinishedLaunching (app, options);
        }

 


Take it further

So now you've scratched the surface of what you can do using Xamarin.Forms, and hopefully found it performant on both platforms. The rest is up to you - we'd love to hear what you've done with it!

Stay tuned in the future for more mobile development topics! For a later post I will show you how to bring this app to Windows as well, using Visual Studio on a Windows PC.

If you're interested in Xamarin or other mobile work, feel free to contact us to see how we could work together, or to join our team!

Clay Schumacher, Senior Software Engineer

Clay Schumacher, Senior Software Engineer

Clay Schumacher is a Senior Software Engineer and Practice Lead of Mobile Development at ISE. Clay lives in Normal, IL where he works from his home office and Slingshot Cowork. For the last four years he has worked to develop mobile solutions that delight our clients and end users. Clay enjoys agile/lean software development, and treats each project team as a startup bent on delivering the best app for each unique problem. He enjoys traveling and playing games with his wife and two daughters.