maui icon indicating copy to clipboard operation
maui copied to clipboard

iOS: App Crashes when ObservableCollection is modified inside a Task

Open sreiss opened this issue 11 months ago • 7 comments

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

  1. Launch the app provided in the repro sample.
  2. 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 avatar Mar 26 '24 11:03 sreiss

@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.

Toomas75 avatar Mar 26 '24 13:03 Toomas75

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.

BenBtg avatar Mar 26 '24 15:03 BenBtg

Can repro this issue at iOS platform on the latest 17.10 preview 2(8.0.14). exception

RoiChen001 avatar Mar 27 '24 03:03 RoiChen001

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.

IsmailHassani avatar Apr 02 '24 05:04 IsmailHassani

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 });        
    });

BenBtg avatar Apr 08 '24 15:04 BenBtg

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);
}

IsmailHassani avatar Apr 08 '24 15:04 IsmailHassani

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: Annotation 2024-04-16 121622

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.

sreiss avatar Apr 16 '24 10:04 sreiss

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.

BenBtg avatar Apr 17 '24 13:04 BenBtg

Based on @BenBtg 's findings I'm going to close this issue

PureWeen avatar Apr 29 '24 15:04 PureWeen