Telerik blogs
xamarin_tips_header

So you are building your next native cross-platform mobile app using Xamarin.Forms? Good for you! You get the benefits of a single C#/XAML codebase that targets all mobile platforms and customize the user experience on each.

The right UI toolset can also help augment your app. When you build your next Xamarin.Forms app, check out the polished controls that come bundled with Telerik UI for Xamarin. If you haven't picked up the Telerik UI bits yet - you can always start a free trial you know. So, what's there to lose?

As you start building your app out though, there are some very real roadblocks to overcome before your app gets functional. You need to organize your app content for optimal usage and preserve the continuity of user experience. You need to have a consistent coding strategy on a couple of fonts and minimize resource usage. Let's get down to business - here's my list of 5 quick developer tips on how to get around the most common roadblocks.

1. Page Navigation

Almost every mobile app is a collection of pages - navigation through the pages is what makes up the user experience. Xamarin.Forms apps are no exception and, thankfully, an easy navigation model is built in. Developers stitch together a stack of pages - one that follows a Last-in-First-out (LIFO) pattern. Navigation from one page to another pushes a page into the stack and returning to the previous page pops it out of the stack. Simple.

All you have to do to leverage the built-in Xamarin.Forms navigation model is to use the NavigationPage class - this manages the stack automatically for you. Using the NavigationPage class adds a navigation bar to the top of the page, complete with title and icons if desired. Users also see a back navigation button as they navigate - this is customized for each mobile platform.

You'll preferably want to use the NavigationPage class from the very first page of your app - which is defined in the AppStart.cs in your shared PCL code. Here's how:

public class App : Application
{
    public App()
    {
        MainPage = new NavigationPage(new HomeView());
    }
    ..
}

I have been building up an aviation app, connecting it to cloud data and using a ListView to display a collection of business jets. Let's add some simple navigation to it.

The first view (HomeView) is already hooked up to the NavigationPage class. We had the Telerik ListView display our list of business jets - let us allow single selections and navigate the user to a detail page. This is the ubiquitous master-detail scenario and we would like to carry the context of the selected business jet onto the detail page. Here's some code:

<TelerikDataControls:RadListView x:Name="BizJetsList" SelectionMode="Single" ItemTapped="BizJet_Selected"
    ...
    ...
</TelerikDataControls:RadListView>
    
private async void BizJet_Selected(object sender, Telerik.XamarinForms.DataControls.ListView.ItemTapEventArgs e)
{
        BizJets selectedBizJet = (BizJets)e.Item;
        await Navigation.PushAsync(new BizJetDetailView(selectedBizJet));
}

Notice the use of PushAsync() method - push the detail page into the navigation stack, carry the selected business jet and take the user to the detail page. As a free perk, the navigation model renders the appropriate Back button, which brings the user back to the ListView page. Here's navigation in my Aviation app:

If you every wanted to handle the back navigation programmatically, simply use the PopAsync() method to remove the navigated page out of the stack, like so:

await Navigation.PopAsync();

2. Tabbed Pages

While there are many ways to organize content in your apps, one of the most common paradigms is tabs - the user just intuitively knows how to use them. With Xamarin.Forms, you have a built-in way to present app content as tabs through the TabbedPages class. The user interface presents a list of tabs customized to each platform and a large detail area. Here's some simple code in our Detail page to display business jet information:

<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Aviation.BizJetDetailView"
        xmlns:MyTabPages="clr-namespace:Aviation;assembly=Aviation">
    <TabbedPage.Children>
        <MyTabPages:ImagePage Title="Your Jet" Icon="PlaneImage" />
        <MyTabPages:DetailsPage Title="Your Luxury" Icon="DetailImage" />
    </TabbedPage.Children>
</TabbedPage>

Notice how we are trying to fill in the Children collection of a TabbedPage - these are individual completely customizable ContentPages, just tied together as a list of Tabs. I like keeping the children pages that make up the Tabs in one place - like in a folder.

Also, for iOS apps, simply setting the Icon property on the tabs adds the image icons, Just make sure to have appropriately sized icons in the Resources folder of your iOS project. The colored state of the icons also comes free.

One of the challenges you may face with tabbed pages is data management - each of the pages is a separate entity, but they together make up a view and often share data. The way I solve this is by using an unified ViewModel and a singleton instance of it.

public class BizJetsDetailViewModel
{
    private static readonly BizJetsDetailViewModel SingletonBJDVM = new BizJetsDetailViewModel();
    private BizJets IndividualBizJet;

    private BizJetsDetailViewModel()
    {
        // No public constructor.
    }

    public static void SetBJDViewModel(BizJets SelectedBizJet)
    {
        SingletonBJDVM.IndividualBizJet = SelectedBizJet;
    }

    public static BizJets GetIndividualBizJet()
    {
        return SingletonBJDVM.IndividualBizJet;
    }
}

Notice how the ViewModel exposes methods to set/get access to the selected BizJet - and this is shared among all children of TabbedPage through the singleton instance. Now the parent detail view may set the selected BizJet as received through navigation context and all children pages get access to it.

public BizJetDetailView(BizJets SelectedBizJet)
{
    BizJetsDetailViewModel.SetBJDViewModel(SelectedBizJet);
    InitializeComponent();
}

3. Data Persistence

As soon as you start building out your Xamarin.Forms app, you'll have to figure out a consistent data persistence strategy - how do you actually store data on the device from your app? Sure you could pull down data from the cloud every time, but then your app will completely stop working when there is no connectivity.

Ideally you want to cache data once it has been pulled down, so your app keeps working graciously without connectivity. Additionally, most apps require some user and app specific data to be persisted between multiple uses of the app.

There are various data persistence strategies when it comes to Xamarin.Forms apps. What you ideally want is a cross-platform model that works everywhere and allows you data management from your shared code. Sure there are SQLLitePCL solutions and Settings Plugins, but the easiest option may be a built-in dictionary for cross-platform data persistence.

All Xamarin.Forms apps use the Application class to bootstrap themselves, and in there is a Properties dictionary. This dictionary can be used to store serialized primitive data in key value pairs. The Properties dictionary is saved to the device automatically and you can access this data from anywhere in your Xamarin.Forms code using Application.Current.Properties.

Here's some code from my ViewModel that pulls in a list of business jets for the corresponding View. Notice the GetAllBizJets() method - it first checks if the data is already cached, and if so, pulls it out of the Properties dictionary. If not, let us go off to the cloud, pull down data and stick the whole collection of objects into the Properties dictionary for app-wide persistence.

public class BizJetsViewModel
{
    private string BSAppId = "<Your Backend Services App Key>";
    private EverliveApp ELHandle;

    public ObservableCollection<BizJets> BizJetsCollection { get; set; }

    public BizJetsViewModel()
    {
        EverliveAppSettings appSettings = new EverliveAppSettings() { AppId = BSAppId, UseHttps = true };
        ELHandle = new EverliveApp(appSettings);
    }

    public async Task<ObservableCollection<BizJets>> GetAllBizJets()
    {
        BizJetsCollection = new ObservableCollection<BizJets>();

        if (Application.Current.Properties.ContainsKey("BizJetsCollection"))
        {
            BizJetsCollection = Application.Current.Properties["BizJetsCollection"] as ObservableCollection<BizJets>;
            return BizJetsCollection;
        }
        else
        {
            var bizJetsManager = ELHandle.WorkWith().Data<BizJets>();
            var allBizJets = await bizJetsManager.GetAll().ExecuteAsync();

            foreach (BizJets serializedBizJet in allBizJets)
            {
                BizJetsCollection.Add(serializedBizJet);
            }

            Application.Current.Properties["BizJetsCollection"] = BizJetsCollection;
            return BizJetsCollection;
        }
    }
}

4. App LifeCycle

Every mobile app goes through several life cycle phases as intermittent usage continues through the day. These change of states offer a great opportunity for the app developer to take appropriate action to ensure the best possible user experience. Xamarin.Forms makes this quite easy with events that are raised as the app changes state - the developer gets to respond in the appropriate event handler.

The Application class, which is where all Xamarin.Forms apps begin life, exposes three virtual methods that can be overridden to handle lifecycle events:

  1. OnStart - Triggered when the application starts up.
  2. OnSleep - Triggered each time the application goes to the background.
  3. OnResume - Triggered when the application is resumed, after being sent to the background.

Look into the AppStart.cs file and you'll find hooks for each of the lifecycle events - perfect opportunities for you to stow away user/app state and preserve continuity of user experience.

public class App : Application
{
    ...

    protected override void OnStart()
    {
    }

    protected override void OnSleep()
    {
    }

    protected override void OnResume()
    {
    }
}

Remember the Properties dictionary we talked about for data persistence? The Properties collection is saved automatically to disk on each mobile platform - during the OnSleep() event. There is however, a new SavePropertiesAsync() method, which can be called to proactively persist the Properties dictionary at any time, in case the app crashes or is killed by the OS. There is no trigger as to when your app terminates - so preserve all your data and state information during OnSleep() and retrieve them back during OnResume().

5. Image Handling

Imagery delights users and carefully used visuals often leads to a great user experience. For Xamarin.Forms app developers, handling images is quite easy - the perfect opportunity to brand your app correctly and keep inviting users back with rich imagery. You may display local images that you ship with your app package or pull them down over the wire.

The Image control in Xamarin.Forms displays images with full customization. Images are often displayed as a part of a page or inside a container, like in my detail page for business jets:

<ContentPage.Content>
    <StackLayout Orientation="Vertical" VerticalOptions="Center">
        <Label x:Name="BizJetName" TextColor="Black" FontAttributes="Bold" HorizontalOptions="Center" />
        <Image x:Name="BizJetImage" Aspect="AspectFit" />
    </StackLayout>
</ContentPage.Content>

One important property of the Image control is the Aspect - this determines how the image is scaled to occupy the display area. The options are:

  1. AspectFill - Scale image to fill the view. Some image parts may be clipped, but no distortion happens.
  2. AspectFit - Scale image to fit the view. Some parts may be left empty (letterboxing).
  3. Fill - Scale image to exactly fill the view. Scaling may not be uniform in X & Y, and may cause image distortion.

Downloading imagery is great way to keep your app content fresh, but you'll want to ensure that you are not misusing the user's data bandwidth. The solution is to cache images once downloaded - and reuse on subsequent views.

Thankfully, caching functionality is built-in with the ImageSource instance from where the Image control downloads/displays the image, unless you turn it off explicitly. You may also control the duration after which the cached images become stale.

Here's some code to display my business jet images - notice how the Source property is data bound and I'm setting my cache to be good for 5 days. You get to adjust image caching to your specific app needs.

public ImagePage()
{
    InitializeComponent();

    BizJets IndividualBizJet = BizJetsDetailViewModel.GetIndividualBizJet();

    BizJetName.Text = IndividualBizJet.AircraftName;
    BizJetImage.Source = new UriImageSource
    {
        Uri = new Uri(IndividualBizJet.AircraftImageURL),
        CachingEnabled = true,
        CacheValidity = new TimeSpan(5, 0, 0, 0)
    };
}

Conclusion

There will be numerous roadblocks as you build your next cross-platform mobile app - Xamarin.Forms helps you out in many ways. Just make sure to bookmark the Docs and have a consistent strategy on the big ticket items - data management, navigation and app organization. Your goal is to ensure a smooth user experience that keeps inviting the user back to your app, over and over again. Good luck!

Want more real-world Xamarin developers tips and tricks? Keep a look out here for a shiny new Whitepaper focused on Xamarin development - for developers, by developers.


SamBasu
About the Author

Sam Basu

Sam Basu is a technologist, author, speaker, Microsoft MVP, gadget-lover and Progress Developer Advocate for Telerik products. With a long developer background, he now spends much of his time advocating modern web/mobile/cloud development platforms on Microsoft/Telerik technology stacks. His spare times call for travel, fast cars, cricket and culinary adventures with the family. You can find him on the internet.

Related Posts

Comments

Comments are disabled in preview mode.