Create a ListView with LoadMoreItemsAsync at the end of the scroll

I have a ListView in my Windows Phone 8.1 application and I can have something like 1000 or more results, so I need to implement the Load More function every time the scroll hits the bottom or some other logic and a natural way of initiating Add more items to the list.
I found that ListView supports ISupportIncrementalLoading and found this implementation: https://marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/ This was the best solution which I found, since it does not indicate a type, i.e. He is common.

My problem with this solution is that when the ListView is Loaded, LoadMoreItemsAsync runs all the time needed to get all the results, which means that the Load user does not start Load Load. I'm not sure what the LoadMoreItemsAsync trigger LoadMoreItemsAsync , but something is wrong because it assumes that this happens when I open the page and load all the items in place, without me doing anything or scrolling. Here's the implementation:
IncrementalLoadingCollection.cs

 public interface IIncrementalSource<T> { Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize); void SetType(int type); } public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() { private T source; private int itemsPerPage; private bool hasMoreItems; private int currentPage; public IncrementalLoadingCollection(int type, int itemsPerPage = 10) { this.source = new T(); this.source.SetType(type); this.itemsPerPage = itemsPerPage; this.hasMoreItems = true; } public bool HasMoreItems { get { return hasMoreItems; } } public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { var dispatcher = Window.Current.Dispatcher; return Task.Run<LoadMoreItemsResult>( async () => { uint resultCount = 0; var result = await source.GetPagedItems(currentPage++, itemsPerPage); if(result == null || result.Count() == 0) { hasMoreItems = false; } else { resultCount = (uint)result.Count(); await dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { foreach(I item in result) this.Add(item); }); } return new LoadMoreItemsResult() { Count = resultCount }; }).AsAsyncOperation<LoadMoreItemsResult>(); } } 

Here is PersonModelSource.cs

 public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> { private ObservableCollection<DatabaseNotificationModel> notifications; private int _type = ""; public DatabaseNotificationModelSource() { // } public void SetType(int type) { _type = type; } public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) { if(notifications == null) { notifications = new ObservableCollection<DatabaseNotificationModel>(); notifications = await DatabaseService.GetNotifications(_type); } return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => { var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize); return result; }); } } 

I changed it a bit because calling my database is asynchronous, and that was the only way I could wait for a request before populating the collection.

And in my DatabaseNotificationViewModel.cs

 IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type); 


Everything works fine, except for the not so normal "Load More". What is wrong in my code?

+5
source share
2 answers

I created a very simplified example of this problem here and raised this problem in the MSDN forums here . Honestly, I do not know why this strange behavior occurs.

What i watched

  • The ListView will call LoadMoreItemsAsync first with the number 1. I assume this is to determine the size of one item so that it can handle the number of items to request the next call.
  • If the ListView works well, the second call to LoadMoreItemsAsync should happen immediately after the first call, but with the correct number of elements (count> 1), and then no more calls to LoadMoreItemsAsync will happen if you scroll down. In your example, however, this may incorrectly call LoadMoreItemsAsync with a score of 1 again.
  • In the worst case, which is actually often seen in your example, this is that the ListView will continue to call LoadMoreItemsAsync with the number 1 again and again, in order until HasMoreItems becomes false, in which case it loaded all the items one at a time. When this happens, there is a noticeable delay in the user interface while the ListView loads the items. However, the user interface thread is not blocked. The ListView simply chases the UI thread with successive calls to LoadMoreItemsAsync .
  • ListView will not always exhaust all items. Sometimes it loads 100, or 200, or 500 items. In each case, the pattern is: many calls to LoadMoreItemsAsync(1) , followed by one call to LoadMoreItemsAsync(> 1) , if not all elements were loaded with previous calls.
  • This is like loading a page.
  • The problem persists on Windows Phone 8.1 as well as on Windows 8.1.

What causes the problem

  • The problem is very short expectations in LoadMoreItemsAsync before adding items to the list (waiting for tasks after adding items to the list is fine).
  • The problem does not occur if you delete all await inside LoadMoreItemsAsync , thereby forcing it to execute synchronously. In particular, if you remove the wrapper dispatcher.RunAsync and await source.GetPagedItems (just mock the elements), then the ListView will behave nicely.
  • By removing all await s, the problem will reappear even if everything you add looks harmless await Task.Run(() => {}) . How strange!

How to fix the problem

If most of the time spent in calling LoadMoreItemsAsync expects an HTTP request for the next page of elements, as I expect most applications, then the problem will not happen. Thus, we can extend the time spent in the method, waiting for Task.Delay(10) , for example:

 await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { foreach (I item in result) this.Add(item); }).AsTask()); 

I just suggested a (hacker) workaround for your example, but not an explanation of why. If anyone knows why this is happening, let me know.

+8
source

This is not the only thing that can cause this problem. If your ListView is inside a ScrollViewer, it will continue to load all items, and ALSO will not be virtualized properly, which will negatively affect performance. The solution is to give your ListView a specific height.

+2
source

Source: https://habr.com/ru/post/1206104/


All Articles