maui
maui copied to clipboard
iOS: App Crashes when ObservableCollection is modified inside a Task
Description
On iOS, When deleting and inserting items of a ObservableCollection
that is bound to a MAUI CollectionView
, the following exception is thrown: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
.
Steps to Reproduce
- Launch the app provided in the repro sample.
- Follow the steps provided in the app.
What is expected: the collection view should be updated with the 26 additions, 2 deletions and the 1 insertion.
What happens: the exception Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
is thrown.
Link to public reproduction project repository
https://github.com/flesarradu/CollectionViewCrashAddRemoveItems
Version with bug
8.0.3 GA
Is this a regression from previous behavior?
No, this is something new
Last version that worked well
Unknown/Other
Affected platforms
iOS
Affected platform versions
iOS 16.4
Did you find any workaround?
No response
Relevant log output
No response
@sreiss You can load data on threadpool thread, but must update UI in UI thread (thread where view is created). Items should be modified in UI thread.
I am able to reproduce this crash using the provided repro when using iOS Simulator but interestingly could not repro the issue when running as a Mac OS Catalyst app.
Can repro this issue at iOS platform on the latest 17.10 preview 2(8.0.14).
This is probably because your trying to change the observable collection from another thread than the UI thread. If you're using the onappearing without Task.Run it will work. Frankly, i don't understand why you're running it inside a task.run. You're just saying run this code in a separate thread. To overcome this you'll need the dispatcher to run this code.
The OnAppearing (bad name btw, should be more like LoadItems for example) should be a Task that should be awaited inside your CollectionPage
What's also is strange, you only instantiate a new Observable Collection if parameter == null. If not items = null, and adding new items to it will result in a null reference exception. As far as i can see, this is actually the case. The only possible way to be able to use this, is if the viewmodel is an singleton instance throughout the application, but as far as i can tell it isn't.
Confirmed that dispatching all the Collection changes to the UIThread avoids the crash. e.g.
MainThread.BeginInvokeOnMainThread(() =>
{
//When we get a parameter means we are coming back to the page
for (int i = 0; i < 26; i++)
{
Items.Add(new EmptyItem { Width = 50 });
}
Items.RemoveAt(0);
Items.RemoveAt(0);
Items.Insert(0, new RealItem { Width = 100 });
});
if the app is a single window app, than this is ok. If you're using a multi-window app you probably should avoid using this mainthread dispatcher. instead use the following and call the dispatcher on the active window/view you want to touch.
public bool Invoke(Action action)
{
if (Application.Current.MainPage is not null && Application.Current.MainPage.Dispatcher is IDispatcher dispatcher)
return dispatcher.Dispatch(action);
else
return Application.Current.Dispatcher.Dispatch(action);
}
Hi,
Thank you for your feedbacks, but the workaround suggested are actually at the opposite of the what the documentation says on the bindings for MAUI : https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/?view=net-maui-8.0.
Especially the following section:
Our app uses the MVVM pattern and, basically, the workaround suggested imply that the VM layer of the architecture should be aware of UI considerations (here handled by MAUI). This means that concerns can't be separated using this method, although the documentation clearly states that Bindings are done for this explicit purpose.
This seems to clearly be a bug in the iOS implementation of MAUI.
As you correctly point out, the documentation states that updates to property bindings are automatically dispatched to the Main thread. However, note this is only for changes to the bound property itself and not to changes that affect any sub-properties within a complex type assigned to that property. For example, if you had a binding to an address property which was a complex type that contained properties such as house number and postcode. Changes to those “sub-properties" would not be automatically dispatched to the Main thread. even if you implemented INotifyPropertyChanged. Only changes to the address property itself (i.e. reassigning a new address class) would be automatically dispatched to the main thread. In the same way the ObservableCollection view is a complex type which provides a helper for change notifications to its list of items. However, it does not include support for dispatching these changes to the main thread. If that is required, you would need to implement your own version of ObservableCollection.
Hope that clears up any confusion.
Based on @BenBtg 's findings I'm going to close this issue