Answered by:
[W8.1]ISupportIncrementalLoading doesn't stop after navigating away

Question
-
Hi there, I suddenly found a strange behavior of collection implementing ISupportIncrementalLoading.
Let's say we have a main page with ISupportIncrementalLoading collection bound to ListView. And we have another page where we can navigate to.
When navigating to main page, the ISupportIncrementalLoading starts loading items until ListView thinks it's enough. I navigate to new page BEFORE ListView loaded all items it needs.
My expected behavior: ListView stops loading new items as the page isn't visible now.
Real behavior: ListView continues to load items endlessly, even after going away from the page. And it won't stop until gets HasMore == false.
Can anyone help with this? This is absolutely wrong behavior.PS
If I while navigation, set in ViewModel the collection to null and then restore it when coming back -- it seams to help, but that is too much to do, I think.
Here's the code of my basic ISupportIncrementalLoading collection:
public abstract class BaseIncrementalSupportCollection<T> :IList<T>,IList,INotifyCollectionChanged, ISupportIncrementalLoading, INotifyPropertyChanged
{
protected readonly List<T> storage;private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
if (isLoading != value)
{
isLoading = value;
RaisePropertyChanged();
}
}
}public bool failed;
public bool IsFailed
{
get { return failed; }
set
{
if (failed != value)
{
failed = value;
RaisePropertyChanged();
}
}
}public bool IsEmpty
{
get { return !HasMoreItems && Count == 0; }
}protected BaseIncrementalSupportCollection()
{
storage = new List<T>();
}public virtual IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return Task.Run(()=>LoadMoreItems(count)).AsAsyncOperation();
}public abstract bool HasMoreItems { get; }
private async Task<LoadMoreItemsResult> LoadMoreItems(uint count)
{
IsLoading = true;
IsFailed = false;
try
{
var items = await LoadMoreItemsOverride(count);
if (items == null)
return new LoadMoreItemsResult() {Count = 0};
if (items.Count > 0)
{
var prevEmptyState = IsEmpty;
foreach (var item in items)
{
var currItem = item;
await DispatchHelper.RunOnUiIfNecessary(async () =>
{
storage.Add(currItem);
RaiseCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, currItem,
storage.Count - 1));
});
}
if(prevEmptyState!=IsEmpty)
RaisePropertyChanged("IsEmpty");}
return new LoadMoreItemsResult() {Count = (uint) items.Count};
}
catch (Exception e)
{
var aggregate = e as AggregateException;
if (aggregate != null)
e = aggregate.Flatten().InnerException;
IsFailed = true;
var handler = OnError;
if (handler != null)
DispatchHelper.RunOnUiIfNecessary(
() => handler(this, new IncrementallCollectionLoadErrorEventArgs(e)));
return new LoadMoreItemsResult() {Count = 0};
}
finally
{
IsLoading = false;
}}
protected virtual void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, e);
}protected abstract Task<IList<T>> LoadMoreItemsOverride(uint count);
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
DispatchHelper.RunOnUiIfNecessary(()=>handler(this, new PropertyChangedEventArgs(propertyName)));
}public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<IncrementallCollectionLoadErrorEventArgs> OnError;
public event NotifyCollectionChangedEventHandler CollectionChanged;
}- Edited by Herro wongMicrosoft contingent staff, Moderator Tuesday, May 5, 2015 6:19 AM tagged title
Saturday, May 2, 2015 8:29 AM
Answers
-
Thanks for your answer, I hope this can be fixed in future versions. I've just found another way, adding additional bool field isStopped with methods Start, ForceStop setting it to false/true. This value is used when getting HasMoreItems like
bool HasMoreItems{get{return !isStopped && DetermineIfHasMore()};}
And simply by calling those to methods I can stop or continue loading the same collection generator.
What do you think about this implementation?
- Marked as answer by AvelN Thursday, May 7, 2015 9:43 AM
Thursday, May 7, 2015 9:42 AM -
Hi AvelN,
Thank you for reporting this issue. I can reproduce it using MSDN code sample from here. https://code.msdn.microsoft.com/windowsapps/Data-Binding-7b1d67b5.
The implementation in this sample has provided how to cancel the Loading approach, you just need to add some code when the main page navigate to others in its OnVavigatedFrom event handler.
1, In order to do this, we should add a property to public the Cancel Token as following code in IncrementalLoadingBase.cs
public CancellationTokenSource token = new CancellationTokenSource(); public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { if (_busy) { throw new InvalidOperationException("Only one operation in flight at a time"); } _busy = true; // use a new call method to instead the default one return LoadMoreItemsAsync(token.Token, count).AsAsyncOperation<LoadMoreItemsResult>(); // default call method // return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count)); }
2, in GeneratorIncrementalLoadingClass.cs file, call a method to cancel the task.
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count) { //add this code to cancel the task c.ThrowIfCancellationRequested(); uint toGenerate = System.Math.Min(count, _maxCount - _count); Debug.WriteLine(string.Format("load {0} at {1}", count.ToString(), DateTime.Now.ToString())); // Wait for work await Task.Delay(10); // This code simply generates var values = from j in Enumerable.Range((int)_count, (int)toGenerate) select (object)_generator(j); Random rnd=new Random(); for (int i = 0; i < values.Count(); i++) { Employee emp = values.ElementAt(i) as Employee; emp.Distance = rnd.Next(0, 101); } _count += toGenerate; Debug.WriteLine("wait for 1 second"); await Task.Delay(1000); return values.ToArray(); }
3, on page navigatefrom event handler, cacel the task before nagivation.
protected override void OnNavigatedFrom(NavigationEventArgs e) { employees.token.Cancel(); }
Please try to edit to meet your code snippet.
Regards,
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.- Marked as answer by AvelN Thursday, May 7, 2015 9:43 AM
Monday, May 4, 2015 5:58 AMModerator
All replies
-
Hi AvelN,
Thank you for reporting this issue. I can reproduce it using MSDN code sample from here. https://code.msdn.microsoft.com/windowsapps/Data-Binding-7b1d67b5.
The implementation in this sample has provided how to cancel the Loading approach, you just need to add some code when the main page navigate to others in its OnVavigatedFrom event handler.
1, In order to do this, we should add a property to public the Cancel Token as following code in IncrementalLoadingBase.cs
public CancellationTokenSource token = new CancellationTokenSource(); public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { if (_busy) { throw new InvalidOperationException("Only one operation in flight at a time"); } _busy = true; // use a new call method to instead the default one return LoadMoreItemsAsync(token.Token, count).AsAsyncOperation<LoadMoreItemsResult>(); // default call method // return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count)); }
2, in GeneratorIncrementalLoadingClass.cs file, call a method to cancel the task.
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count) { //add this code to cancel the task c.ThrowIfCancellationRequested(); uint toGenerate = System.Math.Min(count, _maxCount - _count); Debug.WriteLine(string.Format("load {0} at {1}", count.ToString(), DateTime.Now.ToString())); // Wait for work await Task.Delay(10); // This code simply generates var values = from j in Enumerable.Range((int)_count, (int)toGenerate) select (object)_generator(j); Random rnd=new Random(); for (int i = 0; i < values.Count(); i++) { Employee emp = values.ElementAt(i) as Employee; emp.Distance = rnd.Next(0, 101); } _count += toGenerate; Debug.WriteLine("wait for 1 second"); await Task.Delay(1000); return values.ToArray(); }
3, on page navigatefrom event handler, cacel the task before nagivation.
protected override void OnNavigatedFrom(NavigationEventArgs e) { employees.token.Cancel(); }
Please try to edit to meet your code snippet.
Regards,
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.- Marked as answer by AvelN Thursday, May 7, 2015 9:43 AM
Monday, May 4, 2015 5:58 AMModerator -
Thanks for your answer, I hope this can be fixed in future versions. I've just found another way, adding additional bool field isStopped with methods Start, ForceStop setting it to false/true. This value is used when getting HasMoreItems like
bool HasMoreItems{get{return !isStopped && DetermineIfHasMore()};}
And simply by calling those to methods I can stop or continue loading the same collection generator.
What do you think about this implementation?
- Marked as answer by AvelN Thursday, May 7, 2015 9:43 AM
Thursday, May 7, 2015 9:42 AM