placeholder

Extending glidex.forms on Android to load a placeholder for images that fail to load

Extending glidex.forms on Android to load a placeholder for images that fail to load

What is glidex.forms?

The glidex.forms library is a Xamarin.Forms implementation of Glide, which is one of the quasi standards for imaging on Android (it is even recommended by Google). Luckily, Jonathan Peppers from Microsoft has a passion to improve Xamarin.Android, and Xamarin.Forms takes a big advantage from that as well. He made the Xamarin.Android Binding library as well as the Xamarin.Forms implementation. Like before with Xamarin.Forms.Nuke, I learned about that library because I am substituting my former image caching solution with Akavache.

Why do we need to extend the library?

If you want to load a placeholder that is stored in the resources of your Android project – there is no need. You could just implement a custom hook into the GlideExtensions class of glidex.forms (I’ll show you that one as well). But if you want to load an image from a Xamarin.Forms resource or even a from a font, we’ll need to extend the GlideExtensions class a little bit.

On a side note, I tried to use the existing mechanism of implementing an IGlideHandler for these purposes, but due to timing issues I never was able to load these kinds of placeholders and I moved on by extending the GlideExtensions class.

Show me some code!

Use Android resource

Let’s have a look at how to load an Android resource as a placeholder first (because it is the easiest way). To do so, we just need to create a custom implementation of an IGlideHandler in the Android project, which will then be called by the GlideExtensions implementation:

public class GlideWithAndroidResourcePlaceholder : IGlideHandler
{
    public GlideWithAndroidResourcePlaceholder()
    {
    }

    public bool Build(ImageView imageView, ImageSource source, RequestBuilder builder, CancellationToken token)
    {
        if (builder != null)
        {
            //easiest way - add the image to Android resources ....
            //general placeholder:
            //builder.Placeholder(Resource.Drawable.MSicc_Logo_Base_Blue_1024px_pad25).Into(imageView);

            //error placeholder:
            builder.Error(Resource.Drawable.MSicc_Logo_Base_Blue_1024px_pad25).Into(imageView);

            return true;
        }
        else
            return false;
    }
}

As you can see, the RequestBuilder has already a placeholder mechanism in place. We just hook into that. If you want a general placeholder (shows the image also during download), use the Placeholder method. For failed image loading only, use the Error method. That’s it.

Use Xamarin.Forms resouce/FontImageSource

As we want to be able to load FontImageSource and Xamarin.Forms resources as well however, we need to go another route. Like we did on iOS, we need to add a placeholder property to the Forms class:

public static ImageSource? PlaceholderImageSource { get; private set; }

To fill our ImageSource with either a Xamarin.Forms resource or a FontImageSource, add these two methods as well:

public static void PlaceholderFromResource (string resourceName, Assembly assembly) =>
	PlaceholderImageSource = ImageSource.FromResource (resourceName, assembly);

public static void PlaceholderFromFontImageSource (FontImageSource fontImageSource) =>
	PlaceholderImageSource = fontImageSource;

Now that we have the PlaceholderImageSource in place, we need to extend the GlideExtensions class. First, we implement a Handler for FontImageSource. The Xamarin.Forms handler for FontImageSource provides us a bitmap, which we are going to use as a placeholder. I only got this working with the AsDrawable method, the AsBitmap method always threw an exception because it couldn’t cast the Bitmap to Drawable (I did not investigate that further).

private static async Task<RequestBuilder?> HandleFontImageSource (RequestManager request, Context? context, FontImageSource fontImageSource, CancellationToken token, Func<bool> cancelled)
{
	if (context == null)
		return null;

	var defaultHandler = new Xamarin.Forms.Platform.Android.FontImageSourceHandler ();

	var bitmap = await defaultHandler.LoadImageAsync (fontImageSource, context, token);

	if (token.IsCancellationRequested || cancelled())
		return null;

	if (bitmap == null)
		return null;

	return request.AsDrawable().Load (bitmap);
}

As for the HandleFontImageSource method, I also needed to change the return value of the HandleStreamImageSource method to use the AsDrawable method, otherwise we would get the same exception as with the FontImageSource:

static async Task<RequestBuilder?> HandleStreamImageSource (RequestManager request, StreamImageSource source, CancellationToken token, Func<bool> cancelled)
{ 
        //code omitted for readability

	return request.AsDrawable().Load (memoryStream.ToArray ());
}

The final steps to make it all work are done in the LoadViaGlide method. First, add another RequestBuilder? variable and name it errorBuilder:

RequestBuilder? errorBuilder = null;

After the switch that handles the source parameter, add these lines of code:

switch(Forms.PlaceholderImageSource) {
	case StreamImageSource streamSource:
		errorBuilder = await HandleStreamImageSource (request, streamSource, token, () => !IsActivityAlive (imageView, Forms.PlaceholderImageSource));
		break;
	case FontImageSource fontImageSource:
		errorBuilder = await HandleFontImageSource(request, imageView.Context, fontImageSource, token, () => !IsActivityAlive (imageView, Forms.PlaceholderImageSource));
		break;
	default:
		errorBuilder = null;
		break;
}

if (errorBuilder != null)
	builder?.Error (errorBuilder);

We are handling our PlaceholderImageSource like we do with the ImageSource that we intend to load. If we have a valid errorBuilder, we pass that into the entire process. With that, we have already everything in place. Let’s see how to use it in your app.

How to use it in your Xamarin.Forms – Android project

Head over to the MainActivity class in your Android project and add the following method:

private void AttachGlide()
{
    Android.Glide.Forms.Init(this, null, false);

    //recommended way of loading resource images=>
    //Android.Glide.Forms.Init(this, new GlideWithAndroidResourcePlaceholder(), false);

    //Xamarin Forms resource image
    //Android.Glide.Forms.PlaceholderFromResource("CachedImageTest.MSicc_Logo_Base_Blue_1024px_pad25.png", Assembly.GetAssembly(typeof(MainViewModel)));

    //FontImageSource
    Android.Glide.Forms.PlaceholderFromFontImageSource(new FontImageSource
    {
        Glyph = XfNativeCachedImages.Resources.MaterialDesignIcons.ImageBroken,
        FontFamily = "MaterialDesignIcons",
        Color = Xamarin.Forms.Color.Red
    });
}

The Glide library needs to be initialized. Depending on the method you are using, there are separate ways to do so. The method above shows them all, you just need to change the commented code to play around with it in the sample (link at the end of the post). The mechanism is like the one I used for iOS and the Nuke package.

If you followed along (or downloaded the sample), you should see similar result to this:

Placeholder_Sample_Android_Title
Left: Single image with placeholder, Right: CollectionView placeholder

Conclusion

As we did on iOS, we now also use native caching and image handling on Android. It took me quite some time to get it up and running, but it was worth the effort. The sample has more than five hundred remote images loaded into a CollectionView. Check how smooth the scrolling is (even with DataBinding!).

You can find the sample here on Github, while the modified version of glidex.forms is available here. As always, I hope this post will be helpful for some of you.

Until the next post, happy coding, everyone!

Posted by msicc in Android, Dev Stories, Xamarin, 1 comment
Extending Xamarin.Forms.Nuke on iOS to load a placeholder for images that fail to load

Extending Xamarin.Forms.Nuke on iOS to load a placeholder for images that fail to load

What is Xamarin.Forms.Nuke?

Xamarin.Forms.Nuke is a Xamarin.Forms implementation of Nuke, one of the most advanced image libraries on iOS today. The Xamarin.Forms implementation focuses heavily on caching only, while the original library has a bunch of more features. I learned about this library when I started to look for an alternative to cache images via Akavache which I used before (I never blogged about that part because it wasn’t ready for that, tbh).

Why do we need to extend the library?

The library currently does only one thing (pretty well) – handling the caching of web images. It uses the default settings of the native Nuke library. The Xamarin.Forms implementation overwrites the ImageSourceHandler for UriImageSource and FileImageSource (optionally), but the case of placeholder loading is not intended in the original version. As I have multiple scenarios where a placeholder comes in handy, I decided to extend the library – and maintain my own fork of it from now on. (Maybe there will be a pull request to the original source, too).

Show me some code, finally!

For our extension, we modify the FormsHandler class as well as the ImageSourceHandler class. Let’s have a look at the FormsHandler class first. We are adding a new property for the placeholder:

public static ImageSource PlaceholderImageSource { get; private set; }

With that property in place, let’s add two methods. One method is for loading a placeholder image from an embedded resource file, while the second one is for specifying a FontImageSource.

public static void PlaceholderFromResource(string resourceName, Assembly assembly) =>
    PlaceholderImageSource = ImageSource.FromResource(resourceName, assembly);

public static void PlaceholderFromFontImageSource(FontImageSource fontImageSource) =>
    PlaceholderImageSource = fontImageSource;

I have chosen the FormsHandler class because setting the placeholder is a global thing (in my scenarios, your mileage may vary). That’s already everything in takes in the FormsHandler class, so let’s have a look at the ImageSourceHandler class.

As we are using the default Xamarin.Forms ImageSourceHandler for the resource image (which is a StreamImageSource) and the FontImageSource, we need to add the static fields for them first:

private static readonly StreamImagesourceHandler DefaultStreamImageSourceHandler = new StreamImagesourceHandler();

private static readonly FontImageSourceHandler DefaultFontImageSourcehandler = new FontImageSourceHandler();

Now let’s implement the loading of the placeholder in a separate method:

private static Task<UIImage> LoadPlaceholderAsync()
{
    switch (FormsHandler.PlaceholderImageSource)
    {
        case StreamImageSource streamImageSource:
            FormsHandler.Warn($"loading placeholder from resource");
            return DefaultStreamImageSourceHandler.LoadImageAsync(streamImageSource);
                    
        case FontImageSource fontImageSource:
            FormsHandler.Warn($"loading placeholder from Font");
            return DefaultFontImageSourcehandler.LoadImageAsync(fontImageSource);
        default:
            FormsHandler.Warn($"no valid placeholder found");
            return null;
    }
}

As you can see, there is nothing overly complicated in this method. Based on the type of the placeholder set in the FormsHandler class, we are calling the default Xamarin.Forms implementation for the placeholder image. Let’s take this code into action by changing the LoadImageAsync method of the ImageSourceHandler:

public async Task<UIImage> LoadImageAsync(
    ImageSource imageSource,
    CancellationToken cancellationToken = new CancellationToken(),
    float scale = 1)
{
    var result = await NukeHelper.LoadViaNuke(imageSource, cancellationToken, scale);
    if (result == null)
        result = await LoadPlaceholderAsync();

    return result;
}

As we need to know if the Nukehelper class is able to load the image, we are already running the code by awaiting it at this level. If the result is null, we are loading the placeholder image via our prior implemented method. That’s all we need to do in our forked Xamarin.Forms.Nuke repository.

How to use it in your Xamarin.Forms – iOS project

First, clone my fork (or fork it if you want) of the Xamarin.Forms.Nuke repository and import it into your Xamarin.Forms solution and reference it in your iOS project. Once that is done, we need to initialize the Nuke library (like in the original source) in the AppDelegate‘s FinishedLaunching method:

Xamarin.Forms.Nuke.FormsHandler.Init(true, false);

The second step is to define the placeholder image source. The FontImageSource should be defined after the LoadApplication method. This way, you can the Xamarin.Forms way of loading the font as a resource.

//Resource image
Xamarin.Forms.Nuke.FormsHandler.PlaceholderFromResource("CachedImageTest.MSicc_Logo_Base_Blue_1024px_pad25.png", Assembly.GetAssembly(typeof(MainViewModel)));

//FontImageSource
Xamarin.Forms.Nuke.FormsHandler.PlaceholderFromFontImageSource(new FontImageSource
{
    Glyph = CachedImageTest.Resources.MaterialDesignIcons.ImageBroken,
    FontFamily = "MaterialDesignIcons",
    Color = Color.Red
});

Now use the Xamarin.Forms Image control like you always do. If the image from the web cannot be loaded, you will see the placeholder, like in these two examples:

cached image placeholder sample
Left: Single image failed loading, right image in CollectionView failed loading

With a few additions to the Xamarin.Forms.Nuke library, we have implemented a placeholder mechanism for images that can’t be loaded. As always, I hope this post will be useful for some of you. Now that I have the iOS implementation of a fast cached image with placeholder loading in place, I will turn to Android, where I will try to achieve the same using the Glidex.Forms library and extend it to load a placeholder. There will be a full sample once that is implemented as well. Stay tuned for that one!

Until the next post, happy coding, everyone!
Posted by msicc in Dev Stories, iOS, Xamarin, 2 comments