Forum Xamarin.Android

Only the original thread that created a view hierarchy can touch its views exception Xamarin.Android

I have the following screen:

When I click on Ponentes ImageButton , after few seconds SpeakersActivity opens. After clicking on Ponentes ImageButton I don't want to stay on its screen for several seconds, but I want SpeakersActivity to open immediately with loadingSpinner ProgressBar while the data in SpeakersActivity is loading. I put the code which causes the delay in a thread in the OnCreate method of SpeakersActiviy:

protected override async void OnCreate(Bundle savedInstanceState)
{
    .....
    .....

    loadingSpinner.Visibility = ViewStates.Visible;
    await Task.Run(() =>
    {
        //get all the speakers from the db
        allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");

        //get only the international spakers
        internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();

        //get only the national speakers
        nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();

        //fill in the RecyclerView with data
        speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
        speakersLayoutManager = new LinearLayoutManager(this);
        speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
        speakersAdapter = new SpeakersAdapter(speakers);
        speakersAdapter.ItemClick += OnItemClick;
        speakersRecyclerView.SetAdapter(speakersAdapter);

        loadingSpinner.Visibility = ViewStates.Gone;
    });
    .....
    .....
}

I get Only the original thread that created a view hierarchy can touch its views exception. What is the cause of that exception and how can I achieve what I want? If there is a single command solution as mine:

await Task.Run(() => {.....}); 

it would be better, so if another person reads my code in the future, he doesn't wonder too much what's going on.

Here is the SpeakersActivity.axml :

<LinearLayout>
    <Toolbar>
        <TextView/>
    </Toolbar>
    <LinearLayout>
        <TextView/>
        <TextView/>
    </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id = "@+id/speakersRecyclerView"/>
    <ProgressBar
        android:id = "@+id/loadingSpinner"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:indeterminateDrawable="@drawable/animdraw"
        android:visibility="gone"
        android:layout_gravity="center"/>
</LinearLayout>

I skipped View's attributes because they are irrelevant.

Answers

  • JarvanJarvan Member, Xamarin Team Xamurai

    It seems that you don't update the UI view in the UI thread. Make sure the related function code executed in main thread.

    Task.Run(() => {
        ...
        MainThread.BeginInvokeOnMainThread(() =>
        {
            // Code to run on the main thread
           loadingSpinner.Visibility = ViewStates.Gone;
        });
    });
    

    Tutorial:
    https://docs.microsoft.com/en-us/xamarin/essentials/main-thread

    Refer to:
    https://stackoverflow.com/questions/47041396/only-the-original-thread-that-created-a-view-hierarchy-can-touch-its-views

  • SvetoslavHlebarovSvetoslavHlebarov USMember ✭✭✭

    It's working, the only issue is that the loadingSpinner ProgressBar is spinning during half of the loading time. For example, if it takes 4 seconds to load SpeakersActivity after Ponentes ImageButton has been clicked, the loadingSpinner is spinning for 1 or 2 seconds and then it stays still until SpeakersActivity data is shown, like that:

    Here is the whole SpeakersActivity code:

    [Activity(Label = "SpeakersActivity", ScreenOrientation = ScreenOrientation.Portrait)]
    public class SpeakersActivity : BaseActivity
    {
        TextView internationalSpeakersTextView;
    
        TextView nationalSpeakersTextView;
    
        RecyclerView speakersRecyclerView;
    
        // Layout manager that lays out each card in the RecyclerView:
        RecyclerView.LayoutManager speakersLayoutManager;
    
        // Adapter that accesses the data set (speakers):
        SpeakersAdapter speakersAdapter;
    
        /// <summary>
        /// List that contains all the speakers
        /// </summary>
        List<Speaker> allSpeakers;
    
        /// <summary>
        /// List that contains the international speakers
        /// </summary>
        List<Speaker> internationalSpeakers;
    
        /// <summary>
        /// List that contains the national speakers
        /// </summary>
        List<Speaker> nationalSpeakers;
    
        ProgressBar loadingSpinner;
    
        protected override async void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
    
            SetContentView(Resource.Layout.SpeakersActivity);
    
            Android.Widget.Toolbar toolbar = FindViewById<Android.Widget.Toolbar>(Resource.Id.toolbar);
            toolbar.NavigationOnClick += delegate
            {
                this.OnBackPressed();
            };
    
            loadingSpinner = FindViewById<ProgressBar>(Resource.Id.loadingSpinner);
            loadingSpinner.Visibility = ViewStates.Visible;
            await Task.Run(() =>
            {
                //get all the speakers from the db
                allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");
    
                //get only the international spakers
                internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();
    
                //get only the national speakers
                nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();
    
                MainThread.BeginInvokeOnMainThread(() =>
                {
                    speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
                    speakersLayoutManager = new LinearLayoutManager(this);
                    speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
                    LoadSpeakers(internationalSpeakers);
    
                    loadingSpinner.Visibility = ViewStates.Gone;
                });
            });
    
            internationalSpeakersTextView = FindViewById<TextView>(Resource.Id.internationalSpeakersTextView);
            internationalSpeakersTextView.Click += delegate
            {
                //change TextViews's style when selected/not-selected
                internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
                nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
    
                LoadSpeakers(internationalSpeakers);
            };
    
            nationalSpeakersTextView = FindViewById<TextView>(Resource.Id.nationalSpeakersTextView);
            nationalSpeakersTextView.Click += delegate
            {
                //change TextViews's style when selected/not-selected
                nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
                internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
    
                LoadSpeakers(nationalSpeakers);
            };
        }
    
        /// <summary>
        /// Load speakers inside activity
        /// </summary>
        /// <param name="speakers">speakers</param>
        private void LoadSpeakers(List<Speaker> speakers)
        {
            speakersAdapter = new SpeakersAdapter(speakers);
            speakersAdapter.ItemClick += OnItemClick;
            speakersRecyclerView.SetAdapter(speakersAdapter);
        }
    
        private void OnItemClick(object sender, string speakerResumeUrl)
        {
            var speakerDetailsActivity = new Intent(this, typeof(SpeakerDetailsActivity));
            speakerDetailsActivity.PutExtra("speakerResumeUrl", speakerResumeUrl);
            StartActivity(speakerDetailsActivity);
        }
    
        public override void OnBackPressed()
        {
            StartActivity(typeof(CongressActivity));
            Finish();
        }
    }
    

    Here is ProgressBar view from SpeakersActivity.axml:

    <ProgressBar
         android:id = "@+id/loadingSpinner"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:indeterminateDrawable="@drawable/animdraw"
         android:visibility="gone"
         android:layout_gravity="center"/>
    

    And here is animdraw.xml:

    <?xml version="1.0" encoding="utf-8" ?> 
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/spinner"
        android:fromDegrees="0.0"
        android:pivotX="50.0%"
        android:pivotY="50.0%"
        android:toDegrees="360.0" />
    
  • JarvanJarvan Member, Xamarin Team Xamurai

    Try to create a custom AsyncTask to load the ProgressBar.

    Check the code:

    public class Activity1 : Activity
    {
        ProgressBar progress;
        TextView text;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            // Create your application here
            SetContentView(Resource.Layout.layout1);
    
            progress = FindViewById<ProgressBar>(Resource.Id.progress);
            text = FindViewById<TextView>(Resource.Id.text);
            CustomTask uptask = new CustomTask(this, progress, text);
            uptask.Execute(100);
        }
    
        protected override void OnDestroy()
        {
            base.OnDestroy();
            Console.WriteLine("==OnDestroy==");
        }
    }
    
    public class CustomTask : AsyncTask<int, int, string>
    {
        Activity mcontext;
        ProgressBar _progress;
        TextView _text;
        public CustomTask(Activity context, ProgressBar progress, TextView text)
        {
            this.mcontext = context;
            this._progress = progress;
            this._text = text;
        }
        protected override string RunInBackground(params int[] @params)
        {
            // TODO Auto-generated method stub
            for (int i = 1; i <= 4; i++)
            {
                try
                {
                    System.Threading.Thread.Sleep(1000);
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    Android.Util.Log.Error("lv", e.Message);
                }
                _progress.IncrementProgressBy(25);
                PublishProgress(i * 25);
    
            }
            return "finish";
        }
    
        protected override void OnProgressUpdate(params int[] values)
        {
            _text.Text = values[0] + "";
            Android.Util.Log.Error("lv==", values[0] + "");
        }
        protected override void OnPostExecute(string result)
        {
            mcontext.Title = result;
            _progress.Visibility = ViewStates.Gone;
        }
    }
    

    Refer to:
    https://stackoverflow.com/questions/48164058/how-to-show-progressbar-and-make-it-progressxamarin-android/48182963

  • SvetoslavHlebarovSvetoslavHlebarov USMember ✭✭✭
    edited April 6

    What do I have to change in the code above to make the text(loading..., 25,50,75 etc.) disappear, only the loading spinner to remain on the screen?

  • JarvanJarvan Member, Xamarin Team Xamurai

    What do I have to ... make the text... disappear, only the loading spinner to remain on the screen?

    Remove the TextView parameter from CustomTask class.

    public class Activity1 : Activity
    {
        ProgressBar progress;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            // Create your application here
            SetContentView(Resource.Layout.layout1);
    
            progress = FindViewById<ProgressBar>(Resource.Id.progress);
            CustomTask uptask = new CustomTask(this, progress);
            uptask.Execute(100);
        }
    }
    
    public class CustomTask : AsyncTask<int, int, string>
    {
        Activity mcontext;
        ProgressBar _progress;
        public CustomTask(Activity context, ProgressBar progress)
        {
            this.mcontext = context;
            this._progress = progress;
        }
        protected override string RunInBackground(params int[] @params)
        {
            // TODO Auto-generated method stub
            for (int i = 1; i <= 4; i++)
            {
                try
                {
                    System.Threading.Thread.Sleep(1000);
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    Android.Util.Log.Error("lv", e.Message);
                }
                _progress.IncrementProgressBy(25);
                PublishProgress(i * 25);
    
            }
            return "finish";
        }
    
        protected override void OnProgressUpdate(params int[] values)
        {
            Android.Util.Log.Error("lv==", values[0] + "");
        }
        protected override void OnPostExecute(string result)
        {
            //mcontext.Title = result;
            _progress.Visibility = ViewStates.Gone;
        }
    }
    
Sign In or Register to comment.