roubachof / Sharpnado.CollectionView

A performant list view supporting: grid, horizontal and vertical layout, drag and drop, and reveal animations.
The Unlicense
245 stars 30 forks source link

Only the original thread that created a view hierarchy can touch its views. #43

Closed Hackmodford closed 2 years ago

Hackmodford commented 2 years ago

Platform (please complete the following information):

Describe the bug Using the collectionview with ReactiveUI DynamicData binding causes threading issues. Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views. It didn't seem to matter which thread I observed, or subscribed on. The Xamarin.Forms CollectionView doesn't seem to have this issue.

To Reproduce

// if not observed on the main thread scheduler, items do not appear
// in the list when using the real bluetooth service but will if using
// the simulated service. OBSERVE ON MAIN THREAD SCHEDULER!
recentDevices
  .ObserveOn(RxApp.MainThreadScheduler)
  .SubscribeOn(RxApp.MainThreadScheduler)
  .Bind(out _devices)
  .Subscribe()
  .DisposeWith(Disposables);

Exceptions (if applicable) Copy paste the exception and the stack trace

CollectionViewRenderer.UpdateItemsSource ()
Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views.

JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args)
JniPeerMembers+JniInstanceMethods.InvokeVirtualVoidMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters)
RecyclerView.SetAdapter (AndroidX.RecyclerView.Widget.RecyclerView+Adapter adapter)
CollectionViewRenderer.UpdateItemsSource ()
CollectionViewRenderer.OnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
(wrapper delegate-invoke) <Module>.invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
ReadOnlyObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs args)
ReadOnlyObservableCollection`1[T].HandleCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
ObservableCollectionExtended`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
ObservableCollectionExtended`1[T].<SuspendNotifications>b__10_0 ()
AnonymousDisposable.Dispose ()
SortedObservableCollectionAdaptor`2[TObject,TKey].Adapt (DynamicData.ISortedChangeSet`2[TObject,TKey] changes, DynamicData.Binding.IObservableCollection`1[T] collection)
ObservableCacheEx+<>c__DisplayClass27_0`2[TObject,TKey].<Bind>b__1 (DynamicData.ISortedChangeSet`2[TObject,TKey] changes)
Select`2+Selector+_[TSource,TResult].OnNext (TSource value)
ExceptionServicesImpl.Rethrow (System.Exception exception)
ExceptionHelpers.Throw (System.Exception exception)
<.cctor>b__2_1 (System.Exception ex)
AnonymousObserver`1[T].OnErrorCore (System.Exception error)
ObserverBase`1[T].OnError (System.Exception error)
AutoDetachObserver`1[T].OnErrorCore (System.Exception exception)
ObserverBase`1[T].OnError (System.Exception error)
Sink`1[TTarget].ForwardOnError (System.Exception error)
Select`2+Selector+_[TSource,TResult].OnNext (TSource value)
Sink`1[TTarget].ForwardOnNext (TTarget value)
Synchronize`1+_[TSource].OnNext (TSource value)
AutoDetachObserver`1[T].OnNextCore (T value)
ObserverBase`1[T].OnNext (T value)
Sink`1[TTarget].ForwardOnNext (TTarget value)
ObserveOnObserverLongRunning`1[TSource].Drain ()
<.cctor>b__17_0 (System.Reactive.ObserveOnObserverLongRunning`1[TSource] self, System.Reactive.Disposables.ICancelable cancelable)
<.ctor>b__3_0 (System.Object thisObject)
ConcurrencyAbstractionLayerImpl+<>c.<StartThread>b__8_0 (System.Object itemObject)
ThreadHelper.ThreadStart_Context (System.Object state)
ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx)
ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx)
ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state)
ThreadHelper.ThreadStart (System.Object obj)
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
android.view.ViewRootImpl.checkThread ViewRootImpl.java:8798
android.view.ViewRootImpl.requestLayout ViewRootImpl.java:1606
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.widget.RelativeLayout.requestLayout RelativeLayout.java:380
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
android.view.View.requestLayout View.java:25390
androidx.recyclerview.widget.RecyclerView.requestLayout RecyclerView.java:4586
androidx.recyclerview.widget.RecyclerView.setAdapter RecyclerView.java:1196
roubachof commented 2 years ago

The message does say that the call was not made from the UI thread. So I guess there an issue in the reactive ui implementation or the way you are using it. Maybe the collection view is calling the collection changed events delegates from a Device.BeginInvokeOnMainThread context.

Hackmodford commented 2 years ago

I didn't see a way to specify which thread. I'm pretty sure I only have the observeOn and subscribeOn methods to do that.

My guess is that Xamarin.Forms collection view ensures changes in the android adapter are being done on the UI thread. I do know that's how it's handled in MvvmCross. That way it doesn't matter which thread you make the changes on, the platform code will handle it.

roubachof commented 2 years ago

So it seems those methods are not working as they should ¯_(ツ)_/¯

roubachof commented 2 years ago

From those issues it seems you are not using platform specific reactive ui assemblies, so it defaults to netstandard assembly which doesn't implement the main thread scheduler:

https://github.com/reactiveui/ReactiveUI/issues/2907

Hackmodford commented 2 years ago

So basically because I'm doing this in my core project (.net standard) it's not going to use the android scheduler? Uggggh

roubachof commented 2 years ago

More like you have to add the platform specific version of reactiveui in each of your platform projects... or you can implement (maybe) your own scheduler implemented with Device.BeginInvokeOnMainThread