Using async/await in an IValueConverter in Xamarin.Forms

Let’s start out with a very simple Setting – we have the following Models:
Product
+ Id : string
+ Name: string
+ Image: Image

Image
+ Id: string
+ Filename: string

In our service layer, we have a function that gets all products, and we want to show them on a Xamarin.Forms page, including each image. The Image-Model has no byte-data or whatsoever included. There is a special ImageCacheService implemented, that uses the Image to get the data either from disk (cache) or from a WebApi. This function is implemented using the async/await-pattern. So we have to use an async-context to get the data.
Here is how the ViewModel and Xaml looks like:

 public class MainPageViewModel
    {
        
        private ProductsService _productsService;
        public MainPageViewModel(ProductsService productsService)
        {
            _productsService = productsService;
            InitAsync();
        }

        private async void InitAsync()
        {
            var products = await _productsService.GetProducts();
            products.ForEach(Products.Add);
        }

        public ObservableCollection<Product> Products { get; } = new ObservableCollection<Product>();
    }
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="AsyncConverter.MainPage">
   <CollectionView>
       <CollectionView.ItemTemplate>
           <DataTemplate>
               <Grid ColumnDefinitions="300,*">
                  <Image Source="" /> <!-- ??? how to bind image -->
                  <Label Grid.Column="1" Text="{Binding Name}" />
               </Grid>
           </DataTemplate>
       </CollectionView.ItemTemplate>
   </CollectionView>
</ContentPage>

So how to solve that problem? We somehow need to call await ImageCacheService.GetImageData(Image) on each Image to retrieve the data. The most common approach is to use ViewModels instead of direct Model binding. But we only want to show the product’s images on a page, we don’t want to change something. So why creating two(?) ViewModels that have no added value?

A more sophisticated approach would be to create an IValueConverter implementation, so we can just bind the Image-Model and the converter does the magic to retrieve the image from database/web/file system or wherever it is stored. However, if you try this implementation, you will quickly reach your limits. IValueConverter has no async pendant, so you can’t use it directly. But how about the following idea:

The converter doesn’t return an ImageSource, but it returns a ViewModel, with a property called something like “Result”. And as soon as the image data is retrieved, it will set the Result property and raise a PropertyChanged event, so that the UI can use it.

The implementation will look something like that:

public abstract class AsyncConverter<T> : IValueConverter
    {
        public abstract Task<T> AsyncConvert(object value, Type targetType, object parameter, CultureInfo culture);

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var res = new GenericAsyncViewModel();
            res.Start(AsyncConvert(value, targetType, parameter, culture));
            return res;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private class GenericAsyncViewModel : INotifyPropertyChanged
        {
            private T _result;
            public event PropertyChangedEventHandler PropertyChanged;

            public T Result
            {
                get => _result;
                set
                {
                    _result = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Result)));
                }
            }

            public async void Start(Task<T> task)
            {
                Result = await task;
            }

        }
    }

This is a generic implementation, when we now implement our ImageModelConverter, it looks like that:

 public class ImageModelConverter : AsyncConverter<ImageSource>
    {
        // its a little bit tricky to inject this using IoC, but it's possible
        // but that's not part of this ... so I just create the service here
        public ImageCacheService _imageCacheService = new ImageCacheService();
        public override async Task<ImageSource> AsyncConvert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Models.Image image)
            {
                var imageData = await _imageCacheService.GetImageData(image);
                return ImageSource.FromStream(() => new MemoryStream(imageData));
            }
            return null;
        }
    }

When we use that Converter, it is slightly a little bit different to our usual procedure, we don’t bind the view’s property directly with the converter, but the BindingContext:

<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:AsyncConverter.Converters"
    x:Class="AsyncConverter.MainPage">
 <ContentPage.Resources>
     <converters:ImageModelConverter x:Key="ImageModelConverter"/>
 </ContentPage.Resources>
    <CollectionView ItemsSource="{Binding Products}">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Image BindingContext="{Binding Image, Converter={StaticResource ImageModelConverter}}"
                           Source="{Binding Result}" />
                    <Label Grid.Column="1" Text="{Binding Name}" />

                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>


If you are interested in all of the source code, feel free to check it out on git
https://github.com/ThomasKison/AsyncConverter

…feel free to modify and use…I appreciate any feedback.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.