IMauiContext not found when trying to get bitmap from FontImageSource

Igor Kravchenko 281 Reputation points
2022-05-26T17:10:36.58+00:00

Hi.
I have next error

System.InvalidOperationException: IMauiContext not found.
[mono-rt]    at Microsoft.Maui.Controls.ViewExtensions.RequireMauiContext(Element element, Boolean fallbackToAppMauiContext) in D:\a\_work\1\s\src\Controls\src\Core\ViewExtensions.cs:line 200
[mono-rt]    at Microsoft.Maui.Controls.ViewExtensions.RequireFontManager(Element element, Boolean fallbackToAppMauiContext) in D:\a\_work\1\s\src\Controls\src\Core\ViewExtensions.cs:line 217
[mono-rt]    at Microsoft.Maui.Controls.Compatibility.Platform.Android.FontImageSourceHandler.LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken) in D:\a\_work\1\s\src\Compatibility\Core\src\Android\Renderers\FontImageSourceHandler.cs:line 36

My code:

public static IImageSourceHandler GetHandler(this ImageSource imageSource)
        {
            if (imageSource is FileImageSource)
                return new FileImageSourceHandler();
            if (imageSource is StreamImageSource)
                return new StreamImagesourceHandler();
            if (imageSource is UriImageSource)
                return new ImageLoaderSourceHandler();
            if (imageSource is FontImageSource)
                return new FontImageSourceHandler();
            return null;
        }

        public static Task<Bitmap> ToBitmap(this ImageSource imageSource, CancellationToken cancellationToken = default)
        {
            if (imageSource == null)
                return null;
            IImageSourceHandler handler = imageSource.GetHandler();
            return handler.LoadImageAsync(imageSource, MainActivity.Instance, cancellationToken);
        }

        public static async Task<BitmapDrawable> ToBitmapDrawable(this ImageSource imageSource, CancellationToken cancellationToken = default)
        {
            if (imageSource == null)
                return null;
            Bitmap bitmap = await imageSource.ToBitmap(cancellationToken);
            return new BitmapDrawable(MainActivity.Instance.Resources, bitmap);
        }

I have a custom Entry with image and want to use it:

<controls:MaterialEntry Type="Outlined" Placeholder="Підрозділ">
                    <controls:MaterialEntry.LeadingImageSource>
                        <FontImageSource FontFamily="MaterialOutline"
                                         Glyph="{Static icons:MaterialOutline.Store}"
                                         Color="{DynamicResource OnSurfaceVariantColor}"/>
                    </controls:MaterialEntry.LeadingImageSource>
                </controls:MaterialEntry>

In handler:

public static async void MapLeadingImage(IMaterialEntryHandler handler, IMaterialContainer entry) =>
            handler.PlatformView.StartIconDrawable = await entry.LeadingImageSource.ToBitmapDrawable(new CancellationToken());

Full code here
How can I get bitmap from ImageSource? Thanks.

.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
2,866 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 26,316 Reputation points Microsoft Vendor
    2022-05-27T10:02:52.75+00:00

    Hello,

    I check your sample, and I found you set null if the imagesource is null in ToBitmapDrawable method, try to set it a default instance, refer to the following code:

     public static async Task<BitmapDrawable> ToBitmapDrawable( this ImageSource imageSource, CancellationToken cancellationToken = default)  
            {  
                if (imageSource == null)  
                    return new BitmapDrawable();  
                Bitmap bitmap = await imageSource.ToBitmap(cancellationToken);  
                return new BitmapDrawable(MainActivity.Instance.Resources, bitmap);  
            }  
    

    And try to set your LeadingImageSourceProperty as a BindableProperty, add the following code into your MaterialEntry class:

     public static readonly BindableProperty LeadingImageSourceProperty =  
    BindableProperty.Create(nameof(IMaterialEntry.LeadingImageSource), typeof(ImageSource), typeof(IMaterialEntry));  
    

    Best Regards,
    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Igor Kravchenko 281 Reputation points
    2022-06-09T19:10:23.893+00:00

    Surfing maui sources I have found worth solution.

    public static async Task<Drawable> GetDrawable(this ImageSource imageSource, IElementHandler handler)  
            {  
                IImageSourceServiceProvider imageSourceServiceProvider = handler.GetRequiredService<IImageSourceServiceProvider>();  
                IImageSourceService service = imageSourceServiceProvider.GetImageSourceService(imageSource);  
                IImageSourceServiceResult<PlatformImage> result = await service.GetDrawableAsync(imageSource, handler.MauiContext.Context);  
                return result.Value;  
            }  
    

    This class is internal (as other methods I need from maui framework :) ), I just paste this to my code (maybe better to have this public?)

    #if IOS || MACCATALYST  
    using PlatformImage = UIKit.UIImage;  
    using PlatformView = UIKit.UIView;  
    #elif ANDROID  
    using PlatformImage = Android.Graphics.Drawables.Drawable;  
    using PlatformView = Android.Views.View;  
    #elif WINDOWS  
    using PlatformImage = Microsoft.UI.Xaml.Media.ImageSource;  
    using PlatformView = Microsoft.UI.Xaml.FrameworkElement;  
    #elif TIZEN  
    using PlatformImage = Tizen.UIExtensions.ElmSharp.Image;  
    using PlatformView = ElmSharp.EvasObject;  
    #elif NETSTANDARD || (NET6_0 && !IOS && !ANDROID && !TIZEN)  
    using PlatformImage = System.Object;  
    using PlatformView = System.Object;  
    #endif  
    
    public static class ElementHandlerExtensions  
        {  
            public static IServiceProvider GetServiceProvider(this IElementHandler handler)  
            {  
                var context = handler.MauiContext ??  
                    throw new InvalidOperationException($"Unable to find the context. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");  
      
                var services = context?.Services ??  
                    throw new InvalidOperationException($"Unable to find the service provider. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");  
      
                return services;  
            }  
      
            public static T GetRequiredService<T>(this IElementHandler handler)  
                where T : notnull  
            {  
                var services = handler.GetServiceProvider();  
      
                var service = services.GetRequiredService<T>();  
      
                return service;  
            }  
        }  
    

    Also, this is more MAUI approach then creating handler for each type of ImageSource (Xamarin approach).