XAM270 Data Binding in Xamarin.Forms

Exercise 3: Using value converters (XAM270)

There isn't always a 1:1 mapping between our available data and the user interface that presents that data. Value Converters are the bridge which spans this difference. In this exercise, you'll use a Value Converter to data bind images to our resources.

To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.

Open the starter solution

This exercise is a continuation of the previous exercise. You can use your existing solution or begin from the prior Exercise 2 > Completed solution in your copy of the cloned or downloaded course materials.


Create value converter for the Image.ImageSource property

You'll create a value converter to translate the ImageUrl property on the Flag object into an ImageSource that the Xamarin.Forms image control can display. Specifically, the value converter will load images from your embedded resources located in the FlagData assembly.

  1. Open the FlagExtensions.cs file in the Extensions folder. This is the code you are currently using to load the image from your resources.
  2. Create a new folder in the FunFlacts project called Converters to store our value converter.
  3. Add a new empty class file to the Converters folder named EmbeddedImageConverter.
  4. Implement the IValueConverter interface on the class.

    • The Convert method should turn the inbound value into a string and assume it's the embedded resource ID - use the code located in the FlagExtensions.GetImageSource method to load the resource.
    • The ConvertBack method is only used in two-way bindings; in this case our image URL will never be changed by the UI and so it can throw a NotSupportedException to indicate that it is not available.
    • Since the image is in a different assembly, you need to tell the ImageSource.FromResource method where to find it. You can either hard-code this (like it is in the GetImageSource implementation), or create a public property on the value converter and pass a Type in to load the image data. You'll use this latter approach since it's more flexible - you can load images from any assembly.
    public class EmbeddedImageConverter : IValueConverter
    {
        /// Optional type located in the assembly you want to get the resource
        /// from - if not supplied, the API assumes the resource is located in
        /// this assembly.
        public Type ResolvingAssemblyType { get; set; }
    
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            var imageUrl = (value ?? "").ToString();
            if (string.IsNullOrEmpty(imageUrl))
                return null;
    
            return ImageSource.FromResource(imageUrl,
                ResolvingAssemblyType?.GetTypeInfo().Assembly);
        }
    
        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            throw new NotSupportedException(
              $"{nameof(EmbeddedImageConverter)} cannot be used on two-way bindings.");
        }
    }
    
  5. Open MainPage.xaml and assign a new ResourceDictionary to the ContentPage.Resources property.
  6. Add an instance of your EmbeddedImageConverter class to the resources so you can use it in your binding expression.

    • If you implemented the resolving type property on your converter, you can use {x:Type data:Flag} to pass in the proper type to locate the images.
    • You will need to define your XML namespaces to use the custom types in XAML - if you don't recall the syntax, check the code sample below.
    • Make sure to give the resource a x:Key, such as "irConverter"
    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:local="clr-namespace:FunFlacts"
                 xmlns:eff="clr-namespace:FunFlacts.Effects"
                 xmlns:data="clr-namespace:FlagData;assembly=FlagData"
                 xmlns:cvt="clr-namespace:FunFlacts.Converters"
                 x:Class="FunFlacts.MainPage"
                 Title="Fun with Flags">
    
        <ContentPage.Resources>
            <ResourceDictionary>
                <cvt:EmbeddedImageConverter x:Key="irConverter"
                        ResolvingAssemblyType="{x:Type data:Flag}" />
            </ResourceDictionary>
        </ContentPage.Resources>
        ...
    
  7. Data-bind the Image.Source property to the ImageSource property on the flag with a binding.
  8. Add your converter to the binding - assign it to the Converter property and use the {StaticResource} markup extension to retrieve it from resources using your assigned key.
  9. Remove the code behind which is currently setting the source property. You can also remove the x:Name on the image in XAML since you no longer need to reference it in code behind.
  10. Run the application and make sure the flag is properly displayed - make sure to try a few countries by clicking the up and down arrows in the tool bar.

    <Image HeightRequest="200"
            Source="{Binding ImageUrl, Mode=OneWay, Converter={StaticResource irConverter}}"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            Aspect="AspectFit" />
    

Bind the Picker items

You'll remove the last of the non-databound code-behind by data binding the list of countries to the Picker control in XAML. To do this, you need to provide access to the list of countries loaded by the FlagsRepository. You'll do this by supplying an aggregate object as the BindingContext. This is a common technique used to supply data to your views - and there's a name for the codified pattern: Model-View-ViewModel.

  1. Open MainPage.xaml.cs and locate the InitializeData method. You can remove all the code from this method except the BindingContext assignment.
  2. Next, change the BindingContext assignment to be an anonymous type that contains the countries and the selected flag:

    // Set the binding context to an anonymous type containing both the countries
    // and the current flag. Note: this could also be a real type (like a ViewModel).
    this.BindingContext = new { Countries = repository.Countries, CurrentFlag };
    
  3. Now, you need to fix up our bindings in the XAML. Open the XAML page and fix all the property names for existing bindings - adding CurrentFlag into the property name:

    Date="{Binding CurrentFlag.DateAdopted, Mode=TwoWay}"
    
  4. Finally, you'll bind the countries. Locate the Picker and add a new binding for the ItemsSource property to bind it to the Countries property you created on the anonymous object you assigned to the BindingContext.

    <Picker ItemsSource="{Binding Countries}"
               SelectedItem="{Binding CurrentFlag.Country, Mode=TwoWay}" />
    
  5. Check your XAML and make sure the x:Name is removed on all the controls - you should no longer have any references to any of these specific UI elements in your code. Instead, it's strictly dealing with the BindingContext.
  6. Run the application and make sure it's all still working properly.

Exercise summary

In this exercise, you used value converters to move incompatible property types into bindings - coercing values and loading resources for our UI. Compare the code behind you have left - it is now just behavior, with no UI coupling at all.

You can view the completed solution in the Exercise 3 > Completed folder of your copy of the cloned or downloaded course materials.

Go Back