1

I have a problem. I created this ViewModel for my CollectionView:

public class TemplateListViewModel
{
    public double WidthHeight { get; set; }

    public ICommand LoadTemplates => new Command(MyHandler);
    public int CurrentTemplateCountReceived;
    public bool HitBottomOfList = false;
    public ObservableCollection<TemplateSource> sourceList { get; set; }


    public TemplateListViewModel()
    {
        CurrentTemplateCountReceived = 0;
        sourceList = new ObservableCollection<TemplateSource>();

        var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
        var width = mainDisplayInfo.Width;
        var density = mainDisplayInfo.Density;
        var ScaledWidth = width / density;

        WidthHeight = (ScaledWidth / 2);

        loadingTemplates += onLoadingTemplates;
        LoadTemplateList();
    }

    private event EventHandler loadingTemplates = delegate { };

    private Task LoadTemplateList()
    {
        loadingTemplates(this, EventArgs.Empty);
        return null;
    }

    private async void onLoadingTemplates(object sender, EventArgs args)
    {
        if (HitBottomOfList == false)
        {
            List<Template> templateList = await App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived);

            if (templateList != null)
            {
                foreach (var template in templateList)
                {
                    ImageSource source = ImageSource.FromUri(new Uri("mysite.org/myapp/" + template.FileName));
                    TemplateSource templateSource = new TemplateSource { Id = template.Id, Source = source, WidthHeight = WidthHeight, FileName = template.FileName };
                    sourceList.Add(templateSource);
                }

                CurrentTemplateCountReceived = sourceList.Count;
            }
            else
            {
                HitBottomOfList = true;
            }
        }
    }

    bool handling = false;

    public void MyHandler()
    {
        // already handling an event, ignore the new one
        if (handling) return;

        handling = true;

        LoadTemplateList();

        handling = false;
    }
}

Now what this does is: It collects image locations from my webpage and then creates the ImageSources for those collected images and adds it to the sourceList. Now I also created a RemainingItemsThresholdReachedCommand="{Binding LoadTemplates}" in the xaml, so it collects more data when it almost hits the bottom of the CollectionView, by calling this command: ICommand LoadTemplates => new Command(MyHandler);. This event gets fired a lot of times so I created this handler:

public void MyHandler()
{
    // already handling an event, ignore the new one
    if (handling) return;

    handling = true;

    LoadTemplateList();

    handling = false;
}

That checks if there is already an event being handled.

The problem is that in MyHandler, LoadTemplateList() doesn't get awaited for result, which results in many calls to my webpage, because handling will be set to false immediately!

Now how can I await the LoadTemplateList()?

A. Vreeswijk
  • 822
  • 1
  • 19
  • 57
  • Duplicate of https://stackoverflow.com/questions/15149811/how-to-wait-for-async-method-to-complete – STG Jan 13 '20 at 06:56

2 Answers2

1

Now how can I await the LoadTemplateList()?

With the await keyword:

handling = true;

await LoadTemplateList();

handling = false;

However, it will still return early, because the code is doing some funky private EventHandler stuff. If you just remove all that excess code and move the asynchronous code into LoadTemplateList, it will work fine:

public class TemplateListViewModel
{
    public double WidthHeight { get; set; }

    public ICommand LoadTemplates => new Command(MyHandler);
    public int CurrentTemplateCountReceived;
    public bool HitBottomOfList = false;
    public ObservableCollection<TemplateSource> sourceList { get; set; }


    public TemplateListViewModel()
    {
        CurrentTemplateCountReceived = 0;
        sourceList = new ObservableCollection<TemplateSource>();

        var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
        var width = mainDisplayInfo.Width;
        var density = mainDisplayInfo.Density;
        var ScaledWidth = width / density;

        WidthHeight = (ScaledWidth / 2);

        MyHandler();
    }

    private async Task LoadTemplateList()
    {
        if (HitBottomOfList == false)
        {
            List<Template> templateList = await App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived);

            if (templateList != null)
            {
                foreach (var template in templateList)
                {
                    ImageSource source = ImageSource.FromUri(new Uri("mysite.org/myapp/" + template.FileName));
                    TemplateSource templateSource = new TemplateSource { Id = template.Id, Source = source, WidthHeight = WidthHeight, FileName = template.FileName };
                    sourceList.Add(templateSource);
                }

                CurrentTemplateCountReceived = sourceList.Count;
            }
            else
            {
                HitBottomOfList = true;
            }
        }
    }

    bool handling = false;

    public async void MyHandler()
    {
        // already handling an event, ignore the new one
        if (handling) return;

        handling = true;

        await LoadTemplateList();

        handling = false;
    }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

You should probably use Command with CanExecute with ChangeCanExecute as mentioned here in the documentation

Also Unsubscribe to events by -= before subscribing

And you can make command async and add awaited action as

Command MyCommand = new Command(async () => await ExecuteMyCommand());
Morse
  • 8,258
  • 7
  • 39
  • 64
  • It doesn't seem that my `onLoadingTemplates` gets called using your code, because I added a breakpoint on the first line, but that doesn't get hit! I added the following to my code: `MyCommand.CanExecute(true);`..... What am I doing wrong? – A. Vreeswijk Jan 12 '20 at 00:29
  • initialization of command should have ```CanExecute``` parameter set to a local property. and ExecuteMyCommand signature should be something like ```private async Task ExecuteMyCommand()`{ await ......}``` – Morse Jan 12 '20 at 00:52