An interesting (at least for me) error that I found there.
I am making a (prototype) application, it executes some web requests and returns simple data.
There is an ObservableCollection<DownloadbleEntity> , which is updated dynamically (because DownloadbleEntity contains the image that we get for other requests to display a list item with an image).
Here is part of the layout:
<MvvmCross.Binding.Droid.Views.MvxListView android:id="@+id/searchlist" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" local:MvxBind="ItemsSource FoundItems; ItemClick OnItemClickCommand; OnScrollToBottom GetNewAsyncCommand" local:MvxItemTemplate="@layout/listitem" />
And this is the ViewModel code to show how the update happens:
private ObservableCollection<DownloadableEntity> _foundItems; public ObservableCollection<DownloadableEntity> FoundItems { get { return _foundItems; } set { if (_currentPage > 0) { _foundItems = new ObservableCollection<DownloadableEntity>(_foundItems.Concat(value)); } else { _foundItems = value; } RaisePropertyChanged(() => FoundItems); } } private async Task PrepareDataForOutput(SearchResult searchResult) { _currentListLoaded = false; IMvxMainThreadDispatcher dispatcher = Mvx.Resolve<IMvxMainThreadDispatcher>(); List<Task<DownloadableEntity>> data = searchResult.Tracks.Items.ToList().Select(async (x) => await PrepareDataOutputAsync(x).ConfigureAwait(false)).ToList(); Android.Util.Log.Verbose("ACP", "PrepareDataForOutput"); try { var result = new ObservableCollection<DownloadableEntity>(); while (data.Count > 0) { var entityToAdd = await Task.Run(async () => { Task<DownloadableEntity> taskComplete = await Task.WhenAny(data).ConfigureAwait(false); data.Remove(taskComplete); DownloadableEntity taskCompleteData = await taskComplete.ConfigureAwait(false); await Task.Delay(500); return taskCompleteData; }).ConfigureAwait(false); result.Add(entityToAdd);
Well, the thing is that on devices, after the data is released, it gives out one element of the list and at the same time displays this:
Time Device name Type PID tag message 12-09 13: 45: 22.012 Wileyfox Swift 2 X Info 20385 mvx android.view.ViewRootImpl $ CalledFromWrongThreadException: Only the source thread that created the view hierarchy can touch its Views. in android.view.ViewRootImpl.checkThread (ViewRootImpl.java:6898) in android.view.ViewRootImpl.requestLayout (ViewRootImpl.java:1048) in android.view.View.requestLayout (View.java:19785) at android.view. View.requestLayout (View.java:19785) at android.view.View.requestLayout (View.java:19785) at android.view.View.requestLayout (View .java: 19785) at android.widget.AbsListView.requestLayout (AbsListView.java:1997) in android.widget.AdapterView $ AdapterDataSetObserver.onChanged (AdapterView.java:840) in android.widget.AbsListView $ AdapterDataSetObserver.AnsLonView java: 6380) in android.database.DataSetObservable.notifyChanged (DataSetObservable.java:37) in android.widget.BaseAdapter.notifyDataSetChanged (BaseAdapter.java:50)
But he continues to update my collection, so Android.Util.Log.Verbose("ACP", $"RequestMainThreadAction update {result.Last().Title}"); switches, and I can see it in the device log window.
But all this continues to be displayed on the screen of my device only after I do something - tap the screen or tap the SearchView input or rotate it.
This is strange, I wonder what causes it. Is it because I'm doing something wrong with respect to my collection update?
I recorded a video about what is happening, so here it is (sorry for my English and accent :()
UPD (relative to the last comment):
Is the collection really updated from the background thread if the update happens inside dispatcher.RequestMainThreadAction ?
UPD2
I added a definition for the number of threads, so it seems that the number is always the same
The code:
// as it recommended dispatcher.RequestMainThreadAction(async () => await Task.Run(() => { var poolId = TaskScheduler.Current.Id; Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}"); _toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}"); FoundItems = result; }).ConfigureAwait(false) );
Also in View I added:
protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); _searchView = FindViewById<SearchView>(Resource.Id.search10); ViewModel.OnSearchStartEvent += ViewModel_OnSearchStartEvent; var poolId = TaskScheduler.Current.Id; Android.Util.Log.Verbose("ACP THREAD INFO", $"VIEW CREATED FROM THREAD NUMBER {poolId}"); }
Exit:

In addition, I tried this approach, but failed:
Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity.RunOnUiThread(async () => await Task.Run(() => { var poolId = TaskScheduler.Current.Id; Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}"); _toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}"); FoundItems = result; }).ConfigureAwait(false) );
UPD3
I found one solution (but still not a solution) - if I use MvxObservableCollection instead of ObservableCollection for FoundItems - everything works as it should!
If we look at this class ( MvxObservableCollection.cs ), we will see that it has those functions that trigger updates, it looks like it does the same:
protected virtual void InvokeOnMainThread(Action action) { var dispatcher = MvxSingleton<IMvxMainThreadDispatcher>.Instance; dispatcher?.RequestMainThreadAction(action); } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { InvokeOnMainThread(() => base.OnPropertyChanged(e)); }
But I do not understand why in my case this does not work, as it is supposed, I mean only regular ObservableCollection ?
Is notyfier for an ObservableCollection change creating another thread or what?