Xamarin.Droid and MvvmCross: Setting Fonts in XML

In Android, your options are kind of small for setting a font purely in XML.

image

In this post, I’ll create a converter so we can specify the font in an MvvmCross binding.

Getting a font

Before going any further, you need to actually get and include a font in your android app.  For this example I will be using Fontin.  The main reason I’m using it is because it was easy to find and is free. Download the TTF versions and and include them in your project.  Add a new subdirectory to the Assets folder in your android project named “font”.  Drop the files into that folder and include them in your project.  Make sure that the build action is set to AndroidAsset.

image        image

If you get the following error, make sure that the font names are spelled exactly the same as the folder and file names, including capitalization.

java.lang.RuntimeException: native typeface cannot be made

In the example from my screenshots, my font name should be:  font/Fontin-Italic.ttf 

And again, capitalization matters.

Binding to Typeface

The first step is to use MvvmCross.  Next all you really need to do is bind directly to the Typeface property on the TextView.  They Typeface property is an instance of of Typeface (nothing surprising there) which meanst that you’ll need a converter.

  <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        local:MvxBind="Text Hello; Typeface StringToFont(FontName)" />

The converter is pretty straight forward:

public class StringToFontConverter : MvxValueConverter<string, Typeface>
{
    private static Dictionary<string, Typeface> _cache = new Dictionary<string, Typeface>();

    protected override Typeface Convert(string fontName, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            if (!fontName.StartsWith(@"font/")) fontName = @"font/" + fontName;
            if (!fontName.EndsWith(".ttf")) fontName += ".ttf";

            if (!_cache.ContainsKey(fontName))
            {
                _cache[fontName] = Typeface.CreateFromAsset(Application.Context.Assets, fontName);
            }

            return _cache[fontName];
        }
        catch (Exception e)
        {
            Android.Util.Log.Error("AndroidFont", e.ToString());

            return Typeface.Default;
        }
    }
}

First thing the converter does is to clean the input, ensuring that the font name starts with the directory and ends with the ttf extension. This makes the binding a bit easier in that we don’t have to remember to get the full font path correct.

It then check its static cache to see if it already has an instance of the the font, if not it creates one by calling Typeface.CreateFromAsset.  If creation fails it does some logging and return the default typeface.  This is important because in my testing VisualStudio hang pretty hard under some circumstances where errors were ignored.

Fire this up, and you’ll see that the font is in fact set.

One problem with this example is that we are forcing the ViewModel to know the correct name for the font.  In some cases that’s ok, in others, we won’t want to handle font in the VM layer.  Luckily we can use Tibet binding and just bind to a static string in the xml.  Just remember to surround it with single quotes.

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    local:MvxBind="Text Hello; 
                   Typeface StringToFont('Fontin-Bold')" />

Sample

Here’s a sample layout putting everything together.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:textSize="40dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Fontin-Bold')" />

<!-- Binding to the View Model -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Bind to Typeface:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont(SelectedFont)" />
    <MvxSpinner
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="SelectedItem SelectedFont; ItemsSource FontNames;" />

<!-- Binding to a constant -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Constant Typeface:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Fontin-Bold')" />

<!-- Error -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:text="Error Handling:  " />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="24dp"
        android:layout_marginBottom="24dp"
        android:textSize="24dp"
        local:MvxBind="Text Hello; Typeface StringToFont('Not a font name')" />
</LinearLayout>

The edit box and first text box are bound to the value in the spinner.  The second text box is staticly bound to the bold font.  The last text box is bound to a value that is not a valid font and defaults to the default Android font.

FontBindingSample

Here’s a link to the working project on git.

Happy Coding

Xamarin.Forms: Native Views

To recap, I’m writing a shopping cart app for Windows Phone, Android, and iOS.  The purpose of the app is primarily to let me use Forms.  Each post will build on top of the previous one.

Last time I fiddled with async loading and added an application level menu.  This week I’m going to add native views on Windows Phone and Android using PageRenders.

Recap and Code

This is the tenth post in the series, you can find the rest here:

  • Day 0:  Getting Started (blog / code)
  • Day 1:  Binding and Navigation (blog / code)
  • Day 2:  Frames, Event Handlers, and Binding Bugs (blog / code)
  • Day 3:  Images in Lists (blog / code)
  • Day 4:  Search and Barcode Scanner (blog / code)
  • Day 5:  Dependency Injection (blog / code)
  • Day 6:  Styling (blog / code)
  • Day 7:  Attached Behaviors (blog / code)
  • Day 8:  Writing to Disk (blog / code)
  • Day 9:  App and Action Bars (blog / code)
  • Day 10:  Native Views (blog / code)

For a full index of posts, including future posts, go to the GitHub project page.

About Page

I want to add a quick about page to the app.  I’ll be honest here, I couldn’t think of a great example where the views would be drastically different depending on the platform.  They will probably look almost exactly the same.  Specifically, they will contain two buttons that will take the user to this blog, (specifically this post), or to the GitHub project page.  The WinPhone version will contain two extra labels.  Not overly fancy, but educational enough.

First things first, I’ll add a simple view model:

public class AboutViewModel : BaseViewModel
{
    public AboutViewModel()
    {
        OpenUrlCommand = new Command<string>(s => Device.OpenUri(new Uri(s)));
    }

    public string BlogUrl { get { return @"http://blog.masterdevs.com/xf-day-10/"; } }

    public string CodeUrl { get { return @"https://github.com/jquintus/spikes/tree/master/XamarinSpikes/ShoppingCart"; } }

    public ICommand OpenUrlCommand { get; private set; }
}

 

The only thing remotely interesting here is the Device.OpenUri(…) call.  It does pretty much what you expect it to, namely opens the URI in the native browser.  This view model is so simple that I don’t even really need to inherit from BaseViewModel.  I do anyway just to future proof it and for consistency.

Next thing I need to do is add the AboutPage stub in in the core project (ShoppingCart.csproj).  For reasons I’ll go into a bit later, this can’t be defined in Xaml.

namespace ShoppingCart.Views
{
    public class AboutPage : ContentPage
    {
        public AboutPage()
        {
            Title = "About";
            Content = new Label { Text = "This page is not available for your platform", }; 
        }
    }
}

Nice and simple.  Just set the title and get out of there.

Now all I need to do is wire up a button somewhere to navigate me to this page.  I already have an action bar and app bar on the main CategoriesListPage, so I’ll just add another button there.

<ContentPage.ToolbarItems>
  <ToolbarItem Name="Log Out" Command="{Binding LogOut}"  Order="Primary" Priority="0">
    <ToolbarItem.Icon>
      <OnPlatform x:TypeArguments="FileImageSource"
                  WinPhone="Assets/Logout.png"
                  Android="ic_action_logout.png" />
    </ToolbarItem.Icon>
  </ToolbarItem>

  <ToolbarItem Name="About" 
               Command="{Binding AboutCommand}"  
               Order="Secondary" 
               Priority="0"/>
</ContentPage.ToolbarItems>

I don’t bother with an icon, so I put it in the “Secondary” order.  On WinPhone and Droid this means it will only show up win you hit the three dots to expand the menu.  It’s bound to the “AboutCommand” which just uses the existing navigation system to take you to the AboutPage.

WinPhone

The first step to getting a native page shown is to define a native page.  So here’s the Xaml for my WinPhoneAboutPage.

<phone:PhoneApplicationPage
    x:Class="ShoppingCart.WinPhone.Views.WinPhoneAboutPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="True">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="Shopping Cart"
                       Style="{StaticResource PhoneTextNormalStyle}"
                       Foreground="{StaticResource PhoneAccentBrush}" />
            <TextBlock Text="about" Margin="9,-7,0,0"
                       Style="{StaticResource PhoneTextTitle1Style}"
                       Foreground="{StaticResource PhoneAccentBrush}" />
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="auto" />
                <RowDefinition Height="*" />
                <RowDefinition Height="auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Button Grid.Row="1" Content="Browse Source Code"
                    Command="{Binding OpenUrlCommand}"
                    CommandParameter="{Binding CodeUrl}" />

            <Button Grid.Row="3" Content="Read Blog"
                    Command="{Binding OpenUrlCommand}"
                    CommandParameter="{Binding BlogUrl}" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

 

A very standard view.  The next thing I need to do is to set the DataContext of the page so my bindings actually work.  I’m inclined to follow the MvvmLight model with the ServiceLocator, but in all honesty that seems like a lot of ceremony for what I know will be one instance of a native view in this app.  So, I cheat a little a bit and just manually set the context in the code behind:

public partial class WinPhoneAboutPage : PhoneApplicationPage
{
    public WinPhoneAboutPage()
    {
        this.DataContext = ShoppingCart.App.AboutViewModel;
        InitializeComponent();
    }
}

Now to wire it up I’ll add a PageRenderer:

public class WinPhoneAboutPageRenderer :  Xamarin.Forms.Platform.WinPhone.PageRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
    {
        base.OnElementChanged(e);
        this.Children.Add(new AboutPage());
    }
}

And now that we have the PageRenderer defined, we need to tell the system to actually use it:

[assembly: ExportRenderer(typeof(ShoppingCart.Views.AboutPage), 
                          typeof(ShoppingCart.WinPhone.Views.WinPhoneAboutPageRenderer))]

 

This line can go anywhere in the assembly (just not within a namespace).  A lot of the examples place it in the same file as the renderer.  This has the benefit of keeping it close to where we’re using it.  I’ve elected to add this line at the beginning of the WinPhoneSetup file.  If we wind up with several definitions for renderers, it would be nice to have them all in one place.  I could be wrong about this.

Firing up the emulator and this looks… more than a little wrong.

WP Bad Renderer

So, on my fist pass of the ShoppingCart.AboutPage, I had added a label and two buttons.  When the WinPhoneAboutPageRenderer created the WinPhoneAboutPage, it just overlaid it on top of the existing controls.  Ok, so what if we add a call to Children.Clear()?  This still doesn’t look right, and to show exactly what’s wrong, I’ve added a splash of color to the page.

 

WP Bad Sizing

 

I set the background color of the entire page to red, and of the grid with my buttons to a light green.  As you can see, it’s not exactly taking up the entire page.

Children.Add doesn’t seem to be working for me at all, so I’ll try calling SetNativeControl.  The problem here is that since I’ve inherited from PageRenderer it expects a Xamarin.Forms.Page and I have a Microsoft.Phone.Controls.PhoneApplicationPage.  So I need to change what I’m inheriting from.

public class WinPhoneAboutPageRenderer 
  : VisualElementRenderer<Xamarin.Forms.Page, 
                          Microsoft.Phone.Controls.PhoneApplicationPage>
{
    protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
    {
        base.OnElementChanged(e);
        SetNativeControl(new WinPhoneAboutPage());
    }
}

 

Now that I’m inheriting from the VisualElementRenderer (the base class for the PageRenderer), I can specify that the object I’ll specify to replace the Xamarin.Forms.Page will be a WinPhone page.  Now it’s a simple matter of passing SetNativeControl a new instance of my WinPhoneAboutPage. This winds up looking like what I want.

WP Good

 

Droid About Page

Moving on to Droid, I create an xml file defining my layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <View
        android:layout_height="0dp"
        android:layout_width="fill_parent"
        android:layout_weight="1" />
    <Button
        android:id="@+id/button_blog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Read Blog" />
    <View
        android:layout_height="0dp"
        android:layout_width="fill_parent"
        android:layout_weight="1" />
    <Button
        android:id="@+id/button_code"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Browse Code" />
    <View
        android:layout_height="0dp"
        android:layout_width="fill_parent"
        android:layout_weight="1" />
</LinearLayout>

Again, simple two buttons.  The views are just there as spacers.

And pretty much straight from the samples, here’s my renderer:

public class DroidAboutPageRenderer : PageRenderer
{
    private Android.Views.View _view;

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
    {
        base.OnElementChanged(e);

        AboutViewModel viewModel = App.AboutViewModel;
        var activity = this.Context as Activity;
        _view = activity.LayoutInflater.Inflate(Resource.Layout.AboutLayout, this, false);

        var blogButton = _view.FindViewById<Button>(Resource.Id.button_blog);
        var codeButton = _view.FindViewById<Button>(Resource.Id.button_code);

        blogButton.Click += (sender, ev) => viewModel.OpenUrlCommand.Execute(viewModel.BlogUrl);
        codeButton.Click += (sender, ev) => viewModel.OpenUrlCommand.Execute(viewModel.CodeUrl);
        AddView(_view);
    }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);
        var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
        var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
        _view.Measure(msw, msh);
        _view.Layout(0, 0, r - l, b - t);
    }
}

First things first, I grab the view model from my static cache.  Then I just inflate my view, and start grabbing my buttons so I can add click handlers.  Android doesn’t have a concept of data binding, so adding click handlers is a tad manual.  Once everything is wired up, I add my view to the renderer.  And now I have some errors.

I/MonoDroid( 1596): UNHANDLED EXCEPTION: System.InvalidOperationException: SetElement did not create the correct number of children
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.VisualElementPackager.SetElement (Xamarin.Forms.VisualElement oldElement, Xamarin.Forms.VisualElement newElement) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.VisualElementPackager.Load () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.VisualElementRenderer`1[Xamarin.Forms.Page].SetPackager (Xamarin.Forms.Platform.Android.VisualElementPackager packager) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.VisualElementRenderer`1[Xamarin.Forms.Page].SetElement (Xamarin.Forms.Page element) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.VisualElementRenderer`1[Xamarin.Forms.Page].Xamarin.Forms.Platform.Android.IVisualElementRenderer.SetElement (Xamarin.Forms.VisualElement element) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.RendererFactory.GetRenderer (Xamarin.Forms.VisualElement view) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.NavigationRenderer.SwitchContentAsync (Xamarin.Forms.Page view, Boolean animated, Boolean removed) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.NavigationRenderer.OnPushAsync (Xamarin.Forms.Page view, Boolean animated) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.NavigationRenderer.PushViewAsync (Xamarin.Forms.Page page, Boolean animated) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.Platform.Android.NavigationRenderer.OnPushed (System.Object sender, Xamarin.Forms.NavigationRequestedEventArgs e) [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at Xamarin.Forms.NavigationPage+<PushAsync>d__c.MoveNext () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596): --- End of stack trace from previous location where exception was thrown ---
I/MonoDroid( 1596):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at ShoppingCart.Services.AppNavigation+<ShowAbout>d__4.MoveNext () [0x0001e] in c:\code\Repos\spikes\XamarinSpikes\ShoppingCart\ShoppingCart\ShoppingCart\Services\AppNavigation.cs:35 
I/MonoDroid( 1596): --- End of stack trace from previous location where exception was thrown ---
I/MonoDroid( 1596):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <filename unknown>:0 
I/MonoDroid( 1596):   at ShoppingCart.ViewModels.CategoriesListViewModel+ctor>b__2>d__a.MoveNext () [0x0001b] in c:\code\Repos\spikes\XamarinSpikes\ShoppingCart\ShoppingCart\ShoppingCart\ViewModels\CategoriesListViewModel.cs:39 

The stack trace doesn’t say it, but this error is raised when you call AddView if the ShoppingCart.AboutPage has already had the Content property set.  So, I go back to the AboutPage, and pull out the Content property:

namespace ShoppingCart.Views
{
    public class AboutPage : ContentPage
    {
        public AboutPage()
        {
            Title = "About";
        }
    }
}

Back to the DroidAboutPageRenderer, the OnLayout override is there to make sure that the view is sized to fit the whole screen.  From the top left (0, 0)  to the very bottom right (r-l, b-t)

Don’t forget to register it.  Again, I decided to add this to the top of DroidSetup.cs.

[assembly: ExportRenderer(typeof(ShoppingCart.Views.AboutPage), 
                          typeof(ShoppingCart.Droid.Renderers.DroidAboutPageRenderer))]

Running this up, we get a wonderful (if not pretty) native layout:

droid good

iOS About Page (A Default)

Don’t get too excited.  I still don’t have access to an iDevice.  But I wanted to at least try and make sure that the app wouldn’t crash on iOS.  I’ve updated the core definition of the AboutPage to at least show a label explaining that this page wasn’t available.

public class AboutPage : ContentPage
{
    public AboutPage()
    {
        Title = "About";

        if (Device.OS != TargetPlatform.Android)
        {
            Content = new Label
            {
                Text = "This page is not available for your platform",
            };
        }
    }
}

Since we saw that Android get’s really upset if you set the content in the core version of the page and then try to use a PageRenderer in the platform (at least with my implementation of the renderer), I make sure that we aren’t running on an Android device before setting the content.  The content could have been set to something much more complicated than just a simple label.  It could have even used data bindings like any other page.

Since I don’t have an iPhone, here’s what it looks like on a Droid.

droid unavailble

 

And now we have native views on 2 out of 3 platforms.

Happy Coding

Xamarin.Forms: App and Action Bars

To recap, I’m writing a shopping cart app for Windows Phone, Android, and iOS.  The purpose of the app is primarily to let me use Forms.  Each post will build on top of the previous one.

Last time I wrote data to disk.  This week I’m going to add an application level menu.

Reminder, this article is a direct continuation of last week’s article.  The code base is entirely the same.  The posts were split up so that neither would be too long.  For consistency’s sake, I have created separate releases for each week.

Recap and Code

This is the ninth post in the series, you can find the rest here:

  • Day 0:  Getting Started (blog / code)
  • Day 1:  Binding and Navigation (blog / code)
  • Day 2:  Frames, Event Handlers, and Binding Bugs (blog / code)
  • Day 3:  Images in Lists (blog / code)
  • Day 4:  Search and Barcode Scanner (blog / code)
  • Day 5:  Dependency Injection (blog / code)
  • Day 6:  Styling (blog / code)
  • Day 7:  Attached Behaviors (blog / code)
  • Day 8:  Writing to Disk (blog / code)
  • Day 9:  App and Action Bars (blog / code)

The latest version of the code can always be accessed on the GitHub project page.

Logging Out

Now that I’m all logged in, I need to be able to log out. Ideally this would go in a dedicated page with various settings. But honestly, that’d be boring. This app already has plenty of pages.  What it doesn’t have is a system level menu — an Action Bar on Droid or an App Bar on Windows Phone.

Adding the menu is rather straight forward, just some simple XAML and bindings to CategoriesListPage.xaml:

<ContentPage.ToolbarItems>
  <ToolbarItem Name="Log Out" Command="{Binding LogOut}" Order="Primary" Priority="0">
    <ToolbarItem.Icon>
      <OnPlatform x:TypeArguments="FileImageSource"
                  WinPhone="Assets/Logout.png"
                  Android="ic_action_logout.png" />
    </ToolbarItem.Icon>
  </ToolbarItem>
</ContentPage.ToolbarItems>

Conveniently, ToolbarItem has a Command property that you can bind against to handle clicks.   The command binds the same as a button.  The icon is a bit more involved.

Now the question is: where can I get good icons for my app?  This leads me to a quick aside…

Windows Phone Icons

The easiest way to get icons for a Windows Phone or Metro-style Windows apps is to use Metro Studio by Syncfusion.  First off, it’s free.  Second off, it’s full of great icons.  Just search for whatever you want, and then  customize it for your uses.  You can even create icons out of text in whatever font you like.  Finally, a use for Wingdings.

Metro-Studio

Custom Text

Wingdings

Android Icons

Metro Studio packs a lot of punch, but doesn’t offer any help for Android apps (kinda makes sense given the name).  For Android assets, check out the Android Asset Studio.  There’s a lot of good stuff in here, but for this project I went straight to the Action Bar and Tab Icons section.  They provide a library of images (although significantly smaller than Metro Studio), the ability to upload an image of your choice, or to enter an arbitrary string (and we still get Wingdings!).  The best part is that it generates a zip with assets for various screen resolutions.  Just unzip and drop the folders into the Resources directory.

Image Asset Locations

<ToolbarItem.Icon>
  <OnPlatform x:TypeArguments="FileImageSource"
              WinPhone="Assets/Logout.png"
              Android="ic_action_logout.png" />
</ToolbarItem.Icon>

Because I’m specifying the location of the icon as a string, I need to make sure that the images are placed in the correct locations in the corresponding project folders.  The XF documentation explains pretty clearly where each goes.

For my WinPhone project, I place the Logout.png in the “Assets” folder.

Assets Folder

Android is a little bit more complicated since the platform allows you to serve different sized images for devices with different resolutions.  Each file is put in the corresponding drawable folder under “Resources”.  As I said earlier, the Android Asset Studio does all this for you.  You can just drop the folders it generates directly into the Resources folder.

image

Doing Async Work at Startup

Previously, when the app started up it went directly to the login screen.  Now it has to first check to see if the user is logged in.  If they are then the app goes to the categories page, otherwise it goes to the login page.  This is easy, except for the fact that the check happens on the background thread and takes some time since it is going to disk.  Not a lot of time, but enough that we aren’t guaranteed to get the result back before the app is ready to start up.  In order to accommodate this, I’m turning the “WelcomePage” into a splash page.  It will display a progress dialog for a little bit and then navigate to the next page once it knows which page that is.  Let’s start with the view:

<?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="ShoppingCart.Views.WelcomePage"
             xmlns:local="clr-namespace:ShoppingCart;assembly=ShoppingCart"
             BindingContext="{x:Static local:App.WelcomeViewModel}"
             BackgroundColor="White">

  <StackLayout VerticalOptions="Center"
               IsVisible="{Binding IsLoaded, Mode=OneWayToSource}">
    <Frame>
      <Label Text="Welcome to The Store"
             Font="Bold, Large"
             HorizontalOptions="Center"
             TextColor="{x:Static local:App.AccentColor}" />
    </Frame>

    <ActivityIndicator IsRunning="{Binding IsBusy.IsNotCompleted}"
                       HorizontalOptions="FillAndExpand"
                       VerticalOptions="FillAndExpand"
                       Color="{x:Static local:App.AccentColor}" />
  </StackLayout>
</ContentPage>

The ActivityIndicator starting on line 18 is pretty straight forward.  I bind whether or not it is running to the NotifyTaskCompletion property on the view model (more on that later), and the color to the static representation of the accent color in the same way that I do for the label.  You should remember this from the styling article from a couple of weeks ago.

A little more interesting is the one way to source binding on the visibility of the stack layout.  This is here to tell the view model that the view has been displayed.  Since this is the first page in the XF app it is wrapped in the NavigationPage, which initializes the NavigationService.  If we try to navigate away from this view before it is shown, we have no navigation service, and so navigation won’t work.  Classic Catch-22.

public class WelcomeViewModel : BaseViewModel
{
    private readonly IAppNavigation _navi;
    private SemaphoreSlim _slim;

    public WelcomeViewModel(IAppNavigation navi)
    {
        _navi = navi;
        _slim = new SemaphoreSlim(0, 1);
        IsBusy = new NotifyTaskCompletion<int>(GoToFirstPage());
    }

    public NotifyTaskCompletion<int> IsBusy { get; private set; }

    public bool IsLoaded
    {
        get { return GetValue<bool>(); }
        set
        {
            SetValue(value);
            if (value)
            {
                _slim.Release();
            }
        }
    }

    private async Task<int> GoToFirstPage()
    {
        await _slim.WaitAsync();
        await _navi.SecondPage();
        return 0;
    }
}

The first thing the view model does is initialize a SemaphoreSlim in the unsignaled state, i.e., calling Wait() will block.  It then creates a NotifyTaskCompletion of type int.  Creating this kicks off the async call to GoToFirstPage.  The return value doesn’t matter in this case, the object is just being used to run a background task and update the UI with the status.

When IsLoaded is set to true by the binding to the visibility of the StackLayout in the view, it signals the semaphore, allowing the WaitAsync in GoToFirstPage to complete.  GoToFirstPage then tells the navigation service to show the second page in the app.  The navigation service handles the logic of determining which page to show as well as actually navigating to the page.  For the sake of demonstration, I’ve added a delay in the video below.

Here’s what the app looks like when the user needs to log in

WinPhone Login  Droid Login

And here’s what the returning a user sees

WinPhone Startup Droid Startup

Happy Coding

Xamarin.Forms: Writing to Disk With Akavache

To recap, I’m writing a shopping cart app for Windows Phone, Android, and iOS.  The purpose of the app is primarily to let me use Forms.  Each post will build on top of the previous one.

Last time I added behaviors to my XF xaml.  This week I’m going to save settings to disk using Akavache.

Once I started writing the post to go along with this week’s post, I quickly became aware that the it was longer than what really makes sense for a single post.  So I’m splitting the article in two.  This week will be logging in, next week will be logging out.  Both weeks will use the same code.  For consistency’s sake, I’ll be creating a new release for next week, even though it will point to the same code.

Recap and Code

This is the eight post in the series, you can find the rest here:

  • Day 0:  Getting Started (blog / code)
  • Day 1:  Binding and Navigation (blog / code)
  • Day 2:  Frames, Event Handlers, and Binding Bugs (blog / code)
  • Day 3:  Images in Lists (blog / code)
  • Day 4:  Search and Barcode Scanner (blog / code)
  • Day 5:  Dependency Injection (blog / code)
  • Day 6:  Styling (blog / code)
  • Day 7:  Attached Behaviors (blog / code)
  • Day 8:  Akavache (blog / code)

The latest version of the code can always be accessed on the GitHub project page.

Installing Akavache Cheat Sheet

At the risk of spoiling the narrative below, here’s a very brief outline of the steps to install and use Akavache in a Xamarin Forms app.  For links and more details, continue reading.

  1. Install Akavache package in common PCL project (Install-Package akavache)
  2. Install Akavache package in platform specific projects
    1. Droid
    2. Win Phone
    3. iOS*
  3. WinPhone: Update Splat package (Update-Package splat)
  4. WinPhone:  Change build configuration to x86
  • The iOS step is assumed to be necessary and sufficient to make Akavache work, but I am unable to verify it.

Logging In

From the beginning one of the things that really bothered me about this app is that I have to log in each time I use it.  And that’s including the fact that I don’t have to type in a particular username/password.  Any text will do.  I guess I’m just lazy.

To persist logging in, I’m just going to write a little bit of information to disk when I’m logging in for the first time.  Next time I open the app, I’ll just check for that value, if it exists I’ll go straight to the main landing page.

I don’t want to tie my implementation down to an unknown framework, so the first thing I need to do is create an interface to abstract away persisting data:

public interface ICache
{
    Task<T> GetObject<T>(string key);
    Task InsertObject<T>(string key, T value);
    Task RemoveObject(string key);
}

This interface should handle all of m CRUD operations.  Right now the only person that needs to use it is my LoginService.

public async Task<User> LoginAsync(string username, string password)
{
    User user = Login(username, password);

    await _cache.InsertObject("LOGIN", user);

    return user;
}

public async Task<bool> IsLoggedIn()
{
    User user = await _cache.GetObject<User>("LOGIN");
    return user != null;
}

LoginAsync performs the standard log in check, verifying that username and password are valid credentials (in reality it just checks that they are non-null) and returns a user object if they are valid.  I then save that in the cache using “LOGIN” as the key.  Next, IsLoggedIn in checks to see if there is a non-null value stored in the cache.

Now that we know how the cache is going to be used, let’s implement it.

Akavache

I’ll be using Akavache as my data store.  It’s probably a bit overkill for the very simple use case I have but I have a few reasons why I want to use it.  First, I really don’t want to think about where exactly I’m storing this data.  It could be in a database, it could be on disk, my app doesn’t really care.  It’s just a set of keys and values to me.  While Akavache does use a SQLite backend, I don’t have to know any of the details.  I’m also looking for something that is async/await friendly.  Akavache supports many platforms, I’d love to be able to write this once for all platforms.  And finally, I recently heard about it and kind of wanted to play with it.

Here’s a summary of what I just stated above as my reasons for using Akavache

  • Abstract away storage details
  • async API
  • Cross platform
  • It’s new to me

Akavache on Droid

Akavache is a nuget package, so installation is easy.  I’m installing the package into the core project (ShoppingCart.csproj).  For it to work it also needs to be installed on the Droid project as well.  In my Services folder I’ll add my Akavache implementation of ICache.  This class will be used by all of the platforms.

using Akavache;
using ShoppingCart.Services;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace ShoppingCart.Services
{
    public class Cache : ICache
    {
        public Cache()
        {
            BlobCache.ApplicationName = "ShoppingCart";
        }

        public async Task RemoveObject(string key)
        {
            await BlobCache.LocalMachine.Invalidate(key);
        }

        public async Task<T> GetObject<T>(string key)
        {
            try
            {
                return await BlobCache.LocalMachine.GetObject<T>(key);
            }
            catch (KeyNotFoundException)
            {
                return default(T);
            }
        }

        public async Task InsertObject<T>(string key, T value)
        {
            await BlobCache.LocalMachine.InsertObject(key, value);
        }
    }
}

First thing to note here is that I’m importing the System.Reactive.Linq namespace.  This enables us to await the Akavache calls.  Next thing to note is that I’m setting the ApplicationName property in the constructor.  This is needed to initialize storage.  The rest of the implementation is pretty straight forward.  You can even see that I got most of my method names from the Akavache API.

The RemoveObject implementation invalidates the object in Akavache.  GetObject tries to read the object from the cache, catching any KeyNotFoundExceptions.

Next week I’ll go over how all of this gets plugged into the UI.

Akavache on Win Phone

Akavache is a bit more fiddly on Win Phone.

To start off, install the Akavache nuget package directly in the Win Phone project.  Remember, it’s already installed in my PCL project where it is being used, it needs to be installed here as well, even though none of the code reference it directly.  At this point, I get my first error:

System.TypeInitializationException was unhandled by user code
  HResult=-2146233036
  Message=The type initializer for 'Akavache.BlobCache' threw an exception.
  Source=Akavache
  TypeName=Akavache.BlobCache
  StackTrace:
       at Akavache.BlobCache.set_ApplicationName(String value)
       at ShoppingCart.WinPhone.Services.WinPhoneCache..ctor()
       at lambda_method(Closure , Object[] )
       at Autofac.Core.Activators.Reflection.ConstructorParameterBinding.Instantiate()
  InnerException: System.IO.FileLoadException
       HResult=-2146234304
       Message=Could not load file or assembly 'Splat, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
       Source=Akavache
       StackTrace:
            at Akavache.BlobCache..cctor()
       InnerException:

There is an assembly that can’t be found.  Splat is a another package produced by Paul Betts (the producer of Akavache).  The problem here is not that the file isn’t there, but that it’s an older version.  All I need to do is update the nuget package for Splat and I’m good.

Updating Splat

With this resolved, the next exception I get is that correct version of SQLitePcl.Raw is missing.

System.TypeInitializationException was unhandled by user code
  HResult=-2146233036
  Message=The type initializer for 'Akavache.Sqlite3.Internal.SQLiteConnection' threw an exception.
  Source=Akavache.Sqlite3
  TypeName=Akavache.Sqlite3.Internal.SQLiteConnection
  StackTrace:
       at Akavache.Sqlite3.Internal.SQLiteConnection..ctor(String databasePath, Boolean storeDateTimeAsTicks)
       at Akavache.Sqlite3.SQLitePersistentBlobCache..ctor(String databaseFile, IScheduler scheduler)
       at Akavache.Sqlite3.Registrations.<>c__DisplayClass6.<Register>b__2()
       at System.Lazy`1.CreateValue()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Lazy`1.get_Value()
       at Akavache.Sqlite3.Registrations.<>c__DisplayClass6.<Register>b__3()
       at Splat.ModernDependencyResolver.GetService(Type serviceType, String contract)
       at Splat.DependencyResolverMixins.GetService[T](IDependencyResolver This, String contract)
       at Akavache.BlobCache.get_UserAccount()
       at ShoppingCart.Services.LoginService.get_Users()
       at ShoppingCart.Services.LoginService.<LoginAsync>d__4.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
       at ShoppingCart.ViewModels.LoginViewModel.<Login>d__0.MoveNext()
  InnerException: System.IO.FileNotFoundException
       HResult=-2147024894
       Message=Could not load file or assembly 'SQLitePCL.raw, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
       Source=Akavache.Sqlite3
       FileName=SQLitePCL.raw, Version=0.5.0.0, Culture=neutral, PublicKeyToken=null
       StackTrace:
            at Akavache.Sqlite3.Internal.SQLiteConnection..cctor()
       InnerException:

Again this is a nuget package, but this time updating does not fix the problem.  I reached out to Paul about the issue and he told me that I have to change the build configuration of the Win Phone project to specify x86 as my platform.

Open Configuraton Manager

Configuraton Manager

Now running the app will work.

Thanks

A big thanks to Paul Betts both for writing Akavache and for providing the last mile of help when I needed it.

Happy Coding