Search code examples
windows-phone-8.1windows-store-appsendlessscroll

WP8.1 Universal: Properly cancel and restart LoadMoreItemsAsync from ISupportIncrementalLoading


In order to have endless scrolling in my WP8.1 universal app I've implemented ISupportIncrementalLoading and I'm using the result to bind to my ListView. Works fine with this code:

public sealed class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading
    where T : IIncrementalSource<I>, new()
{
    private int currentPage;
    private bool hasMoreItems;
    private int itemsPerPage;
    private T source;

    public IncrementalLoadingCollection(int itemsPerPage = 10)
    {
        this.source = new T();
        this.itemsPerPage = itemsPerPage;
        this.hasMoreItems = true;
    }

    public bool HasMoreItems
    {
        get { return this.hasMoreItems; }
    }

    public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        return AsyncInfo.Run(c => LoadMoreItemsAsync(c, count));
    }

    private async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken cancellationToken, uint count)
    {
        var dispatcher = Window.Current.Dispatcher;

        var task = await Task.Run<LoadMoreItemsResult>(
            async () =>
            {
                if (Count == 0 && this.currentPage > 0)
                {
                    this.currentPage = 0; // this was cleared
                }
                uint resultCount = 0;

                var result = await this.source.GetPagedItems(cancellationToken, this.currentPage, this.itemsPerPage);

                this.currentPage++;

                if (result == null || !result.Any())
                {
                    this.hasMoreItems = false;
                }
                else
                {
                    resultCount = (uint)result.Count();

                    await dispatcher.RunAsync(
                        CoreDispatcherPriority.Normal,
                        () =>
                        {
                            foreach (I item in result)
                            {
                                Add(item);
                            }
                        });
                }

                return new LoadMoreItemsResult { Count = resultCount };
            }, cancellationToken);
        return task;
    }
}

Now I'm trying to clear all loaded items and load the new data from a different url (user should be able switch the "category") with this code:

public void ClearAndSetNewUrl(string newUrl)
    {
        if (this.LoadMoreItemsAsync((uint) this.itemsPerPage).Status == AsyncStatus.Started)
        {
            this.LoadMoreItemsAsync((uint) this.itemsPerPage).Cancel();
        }
        this.source.SetUrl(newUrl);
        this.hasMoreItems = true;
        base.ClearItems();
    }

But the results are often populated with results from the old url (probably because of async query) or hasMoreItems is set to false and/or incremental loading stops to work.

What's a proper way to get rid of all previous items, stop all tasks that are loading from the old url and start loading only from new url?


Solution

  • Create new instance of your IncrementalLoadingCollection with another arguments (new uri, category, etc.) and reset it for ItemsSource of ListView. So it should help.

    UPDATE

    public class MyViewModel : PropertyChangeBase
    {
          public ThemeEventsDataSource ItemsSource
            {
                get { return _itemsSource; }
                set
                {
                    if (Equals(value, _itemsSource)) return;
                    _itemsSource = value;
                    NotifyOfPropertyChange();
                }
            }
    }
    
    public class ThemeEventsDataSource : ObservableCollection<ThemeEventViewModel>, ISupportIncrementalLoading
        {
            public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
            {
                return AsyncInfo.Run(c => LoadMoreThemeEventsAsync(count));
            }
    
             private async Task<LoadMoreItemsResult> LoadMoreThemeEventsAsync(uint count)
    {
    //TODO: your code for Getting data
    this.Add(...);
    return new LoadMoreItemsResult { Count = (uint)items.Length };
    }
        }
    

    So when, you need change context (changed uri, changed filters, etc.) you should create new ThemeEventsDataSource with specified parametres and notify UI about this.