android.netmaui

How to load image file from (external) storage to .NET MAUI Image?


Setup:

XAML:

<Image x:Name="MyImage"
       HeightRequest="160"
       Aspect="AspectFit"/>

Code-behind:

protected override void OnAppearing()
{
    base.OnAppearing();
    var filePath = Path.Combine(FileSystem.AppDataDirectory, 
                                "test.jpg");
    if (File.Exists(filePath))
    {
        MyImage.Source = ImageSource.FromFile(filePath);
    }
}

It seems the above only works on Windows. It does NOT work on Android.

enter image description here

The following permissions are set in AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

I do NOT want to use FilePicker or anything like that.

The question is plain and simple: how to load image file from storage to .NET MAUI Image?

This GitHub issue seems related and still open since 2022! Anyone knows a workaround or third-party component that sorts this out?


Solution

  • I've been trying to do something similar, and have been seeing the same kinds of wonky behaviors.

    So, you asked if:

    Anyone knows a workaround [...]?

    That's what this is actually, a workaround not a canonical answer. But I've been testing it a lot and it seems really stable on these physical devices:

    It mainly consists of this Refresh() extension that I wrote for the purpose of forcing a reload of image content into the ImageSource that is otherwise aggressively cached and difficult to modify:

    static partial class Extensions
    {
        public static ImageSource? Refresh(this string path)
        {
            if (!string.IsNullOrEmpty(path) && File.Exists(path))
            {
                return ImageSource.FromStream(() => new MemoryStream(File.ReadAllBytes(path)));
            }
            else return null;
        }
    }
    

    screenshots


    Usage (Code-Behind)

    This version of the test app came first, because I wanted to eliminate any variables having to do with the binding of the view model. In other words, in code-behind, using a reference to the Image control, I set its Source property directly. There is no view model at all in this version. To test under "worst" conditions I use a singular file path FixedPathForTest located (from your original post) at Path.Combine(FileSystem.CacheDirectory,"kqeah1iq.yih"). To apply the latest file update, the syntax is:

    imagePhoto.Source = FixedPathForTest.Refresh();


    Usage (ViewModel Binding)

    This version of the test app (the extension doesn't change) came next, to go back to the idea of binding Source in the VM.

    <Image
        Source="{Binding Source}"
        HeightRequest="185"
        Aspect="AspectFit"/>
    

    It's important that the type of Source is ImageSource because even if you think you are setting a string to it, the "real" ImageSource is being implicitly casted from it and the result of the implicit cast is a FileImageSource where the File property is set to the string.


    class MainPageViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// This needs to be ImageSource and not String.
        /// </summary>
        public ImageSource? Source
        {
            get => _imageSource;
            set
            {
                ImageSource? preview = value;
                if (preview is FileImageSource fileImageSource &&
                    fileImageSource.File is string path)
                {
                    var fi = new FileInfo(path);
                    if(!Equals(fi.LastWriteTime, _fileWriteTime))
                    {
                        _imageSource = fileImageSource.File.Refresh();
                        OnPropertyChanged();
                    }
                }
                else
                {
                    if (!Equals(_imageSource, value))
                    {
                        _imageSource = value;
                        OnPropertyChanged();
                    }
                }
            }
        }
        ImageSource? _imageSource = default;
        DateTime _fileWriteTime = default;
    }