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.
- Open the FlagExtensions.cs file in the Extensions folder. This is the code you are currently using to load the image from your resources.
- Create a new folder in the FunFlacts project called Converters to store our value converter.
- Add a new empty class file to the Converters folder named
EmbeddedImageConverter
. -
Implement the
IValueConverter
interface on the class.- The
Convert
method should turn the inboundvalue
into a string and assume it's the embedded resource ID - use the code located in theFlagExtensions.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 aNotSupportedException
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 theGetImageSource
implementation), or create a public property on the value converter and pass aType
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."); } }
- The
-
Open MainPage.xaml and assign a new
ResourceDictionary
to theContentPage.Resources
property. -
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> ...
- If you implemented the resolving type property on your converter, you can use
-
Data-bind the
Image.Source
property to theImageSource
property on the flag with a binding. - 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. - 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. -
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.
- Open MainPage.xaml.cs and locate the
InitializeData
method. You can remove all the code from this method except theBindingContext
assignment. -
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 };
-
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}"
-
Finally, you'll bind the countries. Locate the
Picker
and add a new binding for theItemsSource
property to bind it to theCountries
property you created on the anonymous object you assigned to theBindingContext
.<Picker ItemsSource="{Binding Countries}" SelectedItem="{Binding CurrentFlag.Country, Mode=TwoWay}" />
-
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 theBindingContext
. - 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.