When BindableLayout.ItemSource uses ObservableCollection<T>, it does not marshal ObservableCollection.CollectionChanged to the Main Thread, causing the app to crash whenever an ObservableCollection is modified on a background thread. For example, if we modify the ObservableCollection with Add(), RemoveAt() or Clear() from a background thread, the app will crash.
This behavior is inconsistent with both ListView and CollectionView. When they use an ObservableCollection<T>, they correctly marshal ObservableCollection.CollectionChanged to the Main Thread. For example, if we do the same .Add(), .RemoveAt() or Clear() actions on an ObservableCollection from a background thread, the app will not crash.
The linked sample contains three examples (BindableLayout, ListView and CollectionView) where a user can perform Add(), RemoveAt() and Clear() on each ObservableCollection on a non-UI thread, to demonstrate that this issue only occurs on the BindableLayout:
Open BindableLayoutObservableCollectionRepro.sln in Visual Studio
In Visual Studio, set BindableLayoutObservableCollectionRepro.iOS as the Startup Project
In Visual Studio, build/deploy BindableLayoutObservableCollectionRepro.iOS to an iOS Simulator or Device
Once the app has launched and the "BindableLayout" tab is selected, click on the "Add Item", "Remove Item" or "Clear List" buttons at the bottom of the screen
Confirm that the app crashes
In Visual Studio, set BindableLayoutObservableCollectionRepro.Android as the Startup Project
In Visual Studio, build/deploy BindableLayoutObservableCollectionRepro.Android to an Android Device or Emulator
Once the app has launched and the "BindableLayout" tab is selected, click on the "Add Item", "Remove Item" or "Clear List" buttons at the bottom of the screen
Confirm that the app crashes
Expected Behavior
To be consistent with ListView and CollectionView, the BindableLayout should automatically marshal ObservableCollection.CollectionChanged to the Main Thread
Actual Behavior
BindableLayout does not automatically marshal ObservableCollection.CollectionChanged to the Main Thread, causing the app to crash
Basic Information
Version with issue: Confirmed on Xamarin.Forms v5.x.x.x
Unhandled Exception:
UIKit.UIKitThreadAccessException: UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.
at UIKit.UIApplication.EnsureUIThread () [0x0001a] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:95
at UIKit.UIView.RemoveFromSuperview () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIView.g.cs:1472
at Xamarin.Forms.Platform.iOS.VisualElementPackager.OnChildRemoved (Xamarin.Forms.VisualElement view) [0x00013] in D:\a\1\s\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs:136
at Xamarin.Forms.Platform.iOS.VisualElementPackager.OnChildRemoved (System.Object sender, Xamarin.Forms.ElementEventArgs e) [0x0000f] in D:\a\1\s\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs:178
at Xamarin.Forms.Element.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00007] in D:\a\1\s\Xamarin.Forms.Core\Element.cs:344
at Xamarin.Forms.VisualElement.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\VisualElement.cs:843
at Xamarin.Forms.Layout`1[T].OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:33
at Xamarin.Forms.Layout.OnInternalRemoved (Xamarin.Forms.View view, System.Int32 oldIndex) [0x00012] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:455
at Xamarin.Forms.Layout.InternalChildrenOnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x0002b] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:418
at (wrapper delegate-invoke) <Module>.invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:263
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:182
at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
at Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].RemoveAt (System.Int32 index) [0x00035] in D:\a\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:155
at Xamarin.Forms.BindableLayoutController+<>c__DisplayClass36_0.<ItemsSourceCollectionChanged>b__1 (System.Object item, System.Int32 index) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:302
at Xamarin.Forms.Internals.NotifyCollectionChangedEventArgsExtensions.Apply (System.Collections.Specialized.NotifyCollectionChangedEventArgs self, System.Action`3[T1,T2,T3] insert, System.Action`2[T1,T2] removeAt, System.Action reset) [0x00153] in D:\a\1\s\Xamarin.Forms.Core\Internals\NotifyCollectionChangedEventArgsExtensions.cs:64
at Xamarin.Forms.BindableLayoutController.ItemsSourceCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00021] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:300
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:263
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:182
at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
at BindableLayoutObservableCollectionRepro.Services.ItemSourceService.RemoveItemAsync () [0x00093] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/Services/ItemSourceService.cs:40
at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.BindableLayoutRemoveItemAsync () [0x00025] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:93
at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.<get_BindableLayoutRemoveItemCommand>b__18_0 () [0x0001f] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:56
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/Foundation/NSAction.cs:178
--- End of stack trace from previous location where exception was thrown ---
at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:86
at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0000e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:65
at BindableLayoutObservableCollectionRepro.iOS.Application.Main (System.String[] args) [0x00001] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro.iOS/Main.cs:17
2021-03-20 23:45:36.585268+0800 BindableLayoutObservableCollectionRepro.iOS[14352:382515] Unhandled managed exception: UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread. (UIKit.UIKitThreadAccessException)
at UIKit.UIApplication.EnsureUIThread () [0x0001a] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:95
at UIKit.UIView.RemoveFromSuperview () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIView.g.cs:1472
at Xamarin.Forms.Platform.iOS.VisualElementPackager.OnChildRemoved (Xamarin.Forms.VisualElement view) [0x00013] in D:\a\1\s\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs:136
at Xamarin.Forms.Platform.iOS.VisualElementPackager.OnChildRemoved (System.Object sender, Xamarin.Forms.ElementEventArgs e) [0x0000f] in D:\a\1\s\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs:178
at Xamarin.Forms.Element.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00007] in D:\a\1\s\Xamarin.Forms.Core\Element.cs:344
at Xamarin.Forms.VisualElement.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\VisualElement.cs:843
at Xamarin.Forms.Layout`1[T].OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:33
at Xamarin.Forms.Layout.OnInternalRemoved (Xamarin.Forms.View view, System.Int32 oldIndex) [0x00012] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:455
at Xamarin.Forms.Layout.InternalChildrenOnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x0002b] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:418
at (wrapper delegate-invoke) <Module>.invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:263
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:182
at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
at Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].RemoveAt (System.Int32 index) [0x00035] in D:\a\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:155
at Xamarin.Forms.BindableLayoutController+<>c__DisplayClass36_0.<ItemsSourceCollectionChanged>b__1 (System.Object item, System.Int32 index) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:302
at Xamarin.Forms.Internals.NotifyCollectionChangedEventArgsExtensions.Apply (System.Collections.Specialized.NotifyCollectionChangedEventArgs self, System.Action`3[T1,T2,T3] insert, System.Action`2[T1,T2] removeAt, System.Action reset) [0x00153] in D:\a\1\s\Xamarin.Forms.Core\Internals\NotifyCollectionChangedEventArgsExtensions.cs:64
at Xamarin.Forms.BindableLayoutController.ItemsSourceCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00021] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:300
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:263
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:182
at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
at BindableLayoutObservableCollectionRepro.Services.ItemSourceService.RemoveItemAsync () [0x00093] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/Services/ItemSourceService.cs:40
at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.BindableLayoutRemoveItemAsync () [0x00025] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:93
at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.<get_BindableLayoutRemoveItemCommand>b__18_0 () [0x0001f] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:56
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/Foundation/NSAction.cs:178
--- End of stack trace from previous location where exception was thrown ---
at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:86
at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0000e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/14.0.0.0/src/Xamarin.iOS/UIKit/UIApplication.cs:65
at BindableLayoutObservableCollectionRepro.iOS.Application.Main (System.String[] args) [0x00001] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro.iOS/Main.cs:17
Android Crash
[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Users/builder/jenkins/workspace/archiv03-20 23:22:19.939 I/MonoDroid( 9434): Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views.
[MonoDroid] at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0008e] in <42748fcc36b74733af2d9940a8f3cc8e>:0
[MonoDroid] at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualVoidMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0005d] in <42748fcc36b74733af2d9940a8f3cc8e>:0
[MonoDroid] at Android.Views.ViewGroup.RemoveView (Android.Views.View view) [0x00031] in <227a96d68a0440cea172be41b1306654>:0
[MonoDroid] at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Users/builder/jenki view) [0x0000d] in D:\a\1\s\Xamarin.Forms.Platform.Android\ViewExtensions.cs:30
[MonoDroid] at Xamarin.Forms.Platform.Android.VisualElementPackager.RemoveChild (Xamarin.Forms.VisualElement view) [0x00074] in D:\a\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:273
[MonoDroid] at Xamarin.Forms.Platform.Android.VisualElementPackager.OnChildRemoved (System.Object sender, Xamarin.Forms.ElementEventArgs e) [0x00021] in D:\a\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:243
[MonoDroid] at Xamarin.Forms.BindableLayo03-20 23:22:19.939 I/MonoDroid( 9434): at Xamarin.Forms.Element.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00007] in D:\a\1\s\Xamarin.Forms.Core\Element.cs:344
[MonoDroid] at Xamarin.Forms.VisualElement.OnChildRemoved (Xamarin.Forms.Element child, System.Int32 oldLogicalIndex) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\VisualElement.cs:843
[MonoDroid] at Xamarin.Forms.Layout.OnInternalRemoved (Xamarin.Forms.View view, System.Int32 oldIndex) [0x00012] in D:\a\1\s\Xamarin.Forms.Core\Layout.cs:455
[MonoDroid] at (wrapper delegate-invoke) <Module>.invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
[MonoDroid] at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.BindableLayoutRemoveItemAsync () [0x00025] in /Us03-20 23:22:19.940 I/MonoDroid( 9434): at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
[MonoDroid] at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:182
[MonoDroid] at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
[MonoDroid] at Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].RemoveAt (System.Int32 index) [0x00035] in D:\a\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:155
[MonoDroid] at Xamarin.Forms.BindableLayoutController+<>c__DisplayClass36_0.<ItemsSourceCollectionChanged>b__1 (System.Object item, System.Int32 index) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:302
[MonoDroid] at Xamarin.Forms.Internals.NotifyCollectionChangedEventArgsExtensions.Apply (System.Collections.Specialized.NotifyCollectionChangedEventArgs self, System.Action`3[T1,T2,T3] insert, System.Action`2[T1,T2] removeAt, System.Action reset) [0x00153] in D:\a\1\s\Xamarin.Forms.Core\Internals\NotifyCollectionChangedEventArgsExtensions.cs:64
[chatty] uid=10089(com.[redacted].bindablelayutController.ItemsSourceCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00021] in D:\a\1\s\Xamarin.Forms.Core\BindableLayout.cs:300
[MonoDroid] at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:263
[MonoDroid] at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs:338
[MonoDroid] at System.Collections.ObjectModel.Collection`1[T].RemoveAt (System.Int32 index) [0x00027] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/Collections/ObjectModel/Collection.cs:144
[MonoDroid] at BindableLayoutObservableCollectionRepro.Services.ItemSourceService.RemoveItemAsync () [0x00093] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/Services/ItemSourceService.cs:40
[MonoDroid] at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.BindableLayoutRemoveItemAsync () [0x00025] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:93
[MonoDroid] at BindableLayoutObservableCollectionRepro.ViewModels.MainPageViewModel.<get_BindableLayoutRemoveItemCommand>b__18_0 () [0x0001f] in /Users/[redacted]/Projects/BindableLayoutObservableCollectionRepro/BindableLayoutObservableCollectionRepro/ViewModels/MainPageViewModel.cs:56
[MonoDroid] at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
[MonoDroid] at Android.App.SyncContext+<>c__DisplayClass2_0.<Post>b__0 () [0x00000] in <227a96d68a0440cea172be41b1306654>:0
[MonoDroid] at Java.Lang.Thread+RunnableImplementor.Run () [0x00008] in <227a96d68a0440cea172be41b1306654>:0
[MonoDroid] at Java.Lang.IRunnableInvoker.n_Run (System.IntPtr jnienv, System.IntPtr native__this) [0x00008] in <227a96d68a0440cea172be41b1306654>:0
[MonoDroid] at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr)
[MonoDroid] --- End of managed Android.Util.AndroidRuntimeException stack trace ---
[MonoDroid] android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
[MonoDroid] at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
[MonoDroid] at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[chatty] uid=10089(com.[redacted].bindablelayoutobservablecollectionrepro) identical 4 lines
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[MonoDroid] at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[chatty] uid=10089(com.[redacted].bindablelayoutobservablecollectionrepro) identical 3 lines
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[MonoDroid] at androidx.recyclerview.widget.RecyclerView.requestLayout(RecyclerView.java:4412)
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[chatty] uid=10089(com.[redacted].bindablelayoutobservablecollectionrepro) identical 2 lines
[MonoDroid] at android.view.View.requestLayout(View.java:23093)
[MonoDroid] at android.view.ViewGroup.removeView(ViewGroup.java:5262)
Description
When
BindableLayout.ItemSource
usesObservableCollection<T>
, it does not marshalObservableCollection.CollectionChanged
to the Main Thread, causing the app to crash whenever anObservableCollection
is modified on a background thread. For example, if we modify theObservableCollection
withAdd()
,RemoveAt()
orClear()
from a background thread, the app will crash.This behavior is inconsistent with both
ListView
andCollectionView
. When they use anObservableCollection<T>
, they correctly marshalObservableCollection.CollectionChanged
to the Main Thread. For example, if we do the same.Add()
,.RemoveAt()
orClear()
actions on an ObservableCollection from a background thread, the app will not crash.This behavior occurs in Xamarin.Forms v5.x.x.x and is similar to an old issue reported by @brminnick for the
CollectionView
(see https://github.com/xamarin/Xamarin.Forms/issues/8392)The linked sample contains three examples (BindableLayout, ListView and CollectionView) where a user can perform
Add()
,RemoveAt()
andClear()
on each ObservableCollection on a non-UI thread, to demonstrate that this issue only occurs on the BindableLayout:Xamarin.Forms.BindableLayout
Xamarin.Forms.ListView
Xamarin.Forms.CollectionView
.Add()
&ConfigureAwait(false)
.RemoveAt()
&ConfigureAwait(false)
.Clear()
&ConfigureAwait(false)
Steps to Reproduce
BindableLayoutObservableCollectionRepro.sln
in Visual StudioBindableLayoutObservableCollectionRepro.iOS
as the Startup ProjectBindableLayoutObservableCollectionRepro.iOS
to an iOS Simulator or DeviceBindableLayoutObservableCollectionRepro.Android
as the Startup ProjectBindableLayoutObservableCollectionRepro.Android
to an Android Device or EmulatorExpected Behavior
To be consistent with
ListView
andCollectionView
, theBindableLayout
should automatically marshalObservableCollection.CollectionChanged
to the Main ThreadActual Behavior
BindableLayout
does not automatically marshalObservableCollection.CollectionChanged
to the Main Thread, causing the app to crashBasic Information
Reproduction Link
https://github.com/jvalkyrie/BindableLayoutObservableCollectionRepro
Screenshots
iOS Crash
Android Crash
Environment