Images in CollectionView disappear on scrolling

67 views Asked by At

I am currently working on a CollectionView to display a number of images on .NET MAUI. I am not using any third party library to manage image caching. I am able to start a task that eventually takes advantage of INotifyPropertyChanged in the inner model of DataTemplate to update the ImageSource property of my image and it works pretty well. Unfortunately, though, when I scroll the CollectionView I lose the image from within the Grid cell, despite examining the ObservableCollection<T> which contains the ImageSource property I can find the MemoryStream, and it has not been disposed elsewhere.

I attach the methods I use to retrieve the images and to create the streams below:

// Here's a portion of my ViewModel...

private async Task CaricaDati()
{
    try
    {
        InFetching = true;
        AllFetched = false;

        files = await storageService.GetAllBlobsAsync(idCantiere, "Rapporti");
        
        IEnumerable<AllegatoModel> pageAllegati = (await CaricaDatiSub(0, paginaAllegati)).Select(f => new AllegatoModel
        {
            Nome = f.Name,
            ImageAspect = Utility.GetTipoFile(Path.GetExtension(f.Name)) == Enumerazioni.TipoFile.Immagine ||
                Utility.GetTipoFile(Path.GetExtension(f.Name)) == Enumerazioni.TipoFile.PDF ? Aspect.Fill : Aspect.Center
        });

        if (pageAllegati is not null && pageAllegati.Any())
        {
            Allegati = new ObservableRangeCollection<AllegatoModel>(pageAllegati);
            _ = Task.Run(() => DownloadImagesAsync(pageAllegati));
        }
    }
    catch (Exception ex)
    {
        AppCenterEventManager.TrackError(ex, string.Empty);
        UIMessagesManager.ShowMessage(Stringhe.ATTENZIONE, Stringhe.ERR_IMPREVISTO);
    }
    finally { InFetching = false; }
}

private async Task DownloadImagesAsync(IEnumerable<AllegatoModel> pageAllegati)
{            
    if (pageAllegati is not null)
    {
        foreach (var item in pageAllegati)
        {
            var memStream = await storageService.DownloadStreamAsync(idCantiere, item.Nome);
            Allegati.FirstOrDefault(a => a.Nome == item.Nome).Anteprima = ImageSource.FromStream(() => memStream);
        }
    }
}

// This is my StorageService.cs class...

public async Task<MemoryStream> DownloadStreamAsync(string cName, string bName)
{
    BlobContainerClient container = blobService.GetBlobContainerClient(cName);
    BlobClient file = container.GetBlobClient(bName);

    if (file.Exists())
    {
        MemoryStream str = new();
        await file.DownloadToAsync(str);
        str.Position = 0;
        return str;
    }
    else
        return null;
}

Is there any strategy I am missing? How is it that the Grid cell becomes empty if the StreamImageSource is still there?

1

There are 1 answers

0
ImproveSoftware On

The answer is in the lifetime of the MemoryStream, since it needs to be created in the same context of the CollectionView for it to be kept alive.

Here's the correct way to use it:

private async Task DownloadImagesAsync(IEnumerable<AllegatoModel> pageAllegati)
{            
    if (pageAllegati is not null)
    {
        foreach (var item in pageAllegati)
        {
            MemoryStream memStream = await storageService.DownloadStreamAsync(idCantiere, item.Nome);
            byte[] strArray = Utility.StreamToByteArray(memStream);
            Allegati.FirstOrDefault(a => a.Nome == item.Nome).Anteprima = ImageSource.FromStream(() => new MemoryStream(strArray));
        }
    }
}

You need to pass through a byte array transformation and then go back to Stream.