0

I have following code:

Page with lots of images, that are loaded dynamically with the databinding:

 base.OnAppearing();
        if (!loaded)
        {
            loaded = true;

            BindingContext = new GalleryViewModel(pCode, gCode, gUrl);

        }

viewmodel:

namespace GalShare.ViewModel
{
class GalleryViewModel
{
    public string pCode { get; set; }
    public string gCode { get; set; }
    public string gUrl { get; set; }
    public ObservableCollection<picdata> Galleries { get; set; }          
    public  GalleryViewModel(string pCode, string gCode, string gUrl)
    {
        this.pCode = pCode;
        this.gCode = gCode;
        this.gUrl = gUrl;
        Galleries = new GalleryService().GetImageList(pCode,gCode,gUrl);

    }

}
}

galleryservice.cs

   class GalleryService
 {

    public ObservableCollection<picdata> Images { get; set; }
    public ObservableCollection<picdata> GetImageList(string pCode, string gCode, string gUrl)
    {
        WebClient client = new WebClient();
        Images = new ObservableCollection<picdata>();
        string downloadString = client.DownloadString(gUrl);
        var deserialized = JsonConvert.DeserializeObject<JsonTxt>(downloadString);

            foreach (File img in deserialized.Files)
            {
               Images.Add(new picdata()
               {
                    ImageName = img.file,
                    BaseUrl = deserialized.Settings.Path.ToString(),
                    ThumbUrl = deserialized.Settings.Path.ToString() + "/thumbs" + img.file
               });
            }
        return Images;
    }
}

XAML of the page:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:vm="clr-namespace:GalShare.ViewModel"
         xmlns:d="http://xamarin.com/schemas/2014/forms/design"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
         mc:Ignorable="d"
         x:Class="GalShare.Views.Gallery">

<StackLayout>
    <CollectionView ItemsSource="{Binding Galleries}" x:Name="myCollection" SelectionMode="Single" SelectionChanged="CollectionView_SelectionChanged">
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical"
                    Span="2" />
        </CollectionView.ItemsLayout>
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <ffimageloading:CachedImage Source="{Binding ThumbUrl}" CacheDuration="1" HorizontalOptions="Fill" VerticalOptions="Fill" DownsampleToViewSize="False"></ffimageloading:CachedImage>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</StackLayout>

</ContentPage>

The code works, but given that the images are loaded from the web, while I am loading the data the App is locked. How can this be done asynchronously? I'd like to load the destination page, and then load the content while I'm there.. Currently the app freezes on the page that loads this one until all is loaded.

I have tried with tasks/await but with no success. I think i have to move some things around to run the code asynchronously but cannot figure out how.

7
  • I believe I previously suggested FFImageLoading to you, which is designed specifically to help with this sort of issue. Did you try it? Commented Mar 22, 2020 at 20:34
  • Yes, I am using it on another page, but (if I didnt misunderstand something) then it does not load asynchronously, but only caches the data so the next time it will be faster.. am I right? Commented Mar 22, 2020 at 20:41
  • Actually, I am also using it on this page... I update the code to show you Commented Mar 22, 2020 at 20:43
  • @Jason forgot to tag you in the previous comments.. Commented Mar 22, 2020 at 20:57
  • 1
    Getting data from web while loading UI was the issue check cheesebaron's answer. Commented Mar 23, 2020 at 2:22

1 Answer 1

4

You've tagged async-await and writting asynchronous in your title. However, all of your code is running on the main thread and not asynchronously.

Instead of loading your data in the constructor of the ViewModel. I highly suggest you use a lifecycle event such as OnAppearing on your Page and fire a ICommand to load your data asynchronously.

Additionally I would switch to using HttpClient and its nice async APIs. So something like:

public class GalleryService
{
    private HttpClient _httpClient;

    public GalleryService()
    {
        _httpClient = new HttpClient();
    }

    public async Task<IEnumerable<picdata>> GetImageList(string pCode, string gCode, string gUrl)
    {
        var response = await _httpClient.GetAsync(gUrl).ConfigureAwait(false);
        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            var deserialized = JsonConvert.DeserializeObject<JsonTxt>(json);

            var images = new List<picdata>();
            foreach(var img in deserialized.Files)
            {
                images.Add(new picdata()
                {
                    ImageName = img.file,
                    BaseUrl = deserialized.Settings.Path.ToString(),
                    ThumbUrl = deserialized.Settings.Path.ToString() + "/thumbs" + img.file
                });
            }

            return images;
        }

        return new picdata[0]; // return empty set
    }
}

and ViewModel:

public class GalleryViewModel
{
    private GalleryService _galleryService;

    public ObservableCollection<picdata> Galleries { get; } = new ObservableCollection<picdata>();
    public ICommand GetImagesCommand { get; }

    public GalleryViewModel(string pCode, string gCode, string gUrl)
    {
        _galleryService = new GalleryService();

        GetImagesCommand = new Command(async () => DoGetImagesCommand(pCode, gCode, gUrl));
    }

    private async Task DoGetImagesCommand(string pCode, string gCode, string gUrl)
    {
        var images = await _galleryService.GetImageList(pCode, gCode, gUrl);
        foreach(var image in images)
            Galleries.Add(image);
    }
}

Then in your OnAppearing() override on your page you can call something like: (BindingContext as GalleryViewModel).GetImagesCommand.Execute(null);

Make sure you set your BindingContext before trying to call the command. This can be done in the Page constructor with:

BindingContext = new GalleryViewModel();

This way you are not blocking your entire UI until it is done downloading the images. Alternatively you could fire off a Task with Task.Run in the constructor of the ViewModel. However, then you will have to marshal the population of the ObservableCollection to the UI thread.

Sign up to request clarification or add additional context in comments.

10 Comments

Thank you very much! I am trying to add this to my code to test it and try to understand it, but there are 2 Problems: response.IsSuccessfulStatusCode does not exist.. i replaced it with response.IsSuccessStatusCode.. is this the same? second problem: var json = response.Content.GetStringAsync().ConfigureAwait(false); GetStringAsync does also not exist. i Replaced it with ReadAsStringAsync, but then the next line says cannot convert from 'System.Runtime.CompilerServices.ConfiguredTaskAwaitable<string>' to 'string'
Ah, I wrote this code without an IDE to help me. For the last issue you need to await the ReadAsStringAsync
Thank you, now it compiles without error, but on the line "(BindingContext as GalleryViewModel).GetImagesCommand.Execute(null);" i get a NullreferenceException
And if you set a breakpoint and see if anything is added to Galleries?
Sorry, edited the comment above, but you were typing at the same time..
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.