5

I'm having quite some trouble implementing a functionality as simple as showing an ActivityIndicator while a page is loading, it's been proving very difficult.

This is the code I'm using on App.xaml.cs:

public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new NavigationPage(new LoginPage());
        }
    }

And this is my LoginPage

public partial class LoginPage : ContentPage
    {
        public LoginPage()
        {
            InitializeComponent();

            btnLogin.Clicked += BtnLogin_Clicked;
        }

        protected async void BtnLogin_Clicked(object sender, EventArgs e)
        {
    loadingView.IsVisible = true;
    activityIndicator.IsRunning = true;

            await Navigation.PushAsync(new SchedulePage());

    loadingView.IsVisible = false;
    activityIndicator.IsRunning = false;
        }
    }

Without getting into much detail, the loadingView is a view I have on the xaml file, which houses my ActivityIndicator. My main problem here is that when I call Navigation.PushAsync(), the animation of the ActivityIndicator is halted. According to what I've read this happens because both operations happen on the Main thread, so one interrupts the other.

The reason I need to show the indicator, is because my SchedulePage takes a lot of time to render, since it has an XLabs calendar control.

How would you go about implementing something like this?

Tharkius
  • 2,744
  • 2
  • 20
  • 31
  • The UI has to be initialized on the main thread, so you cannot put that out of the way. What you can do is make sure the data-loading and pupulating the calendar control happens in a asynchronous way. – Kai Brummund Jul 25 '16 at 14:43
  • That would involve surfing through all the complex classes inside the CalendarView source, and figuring out how it all comes together, and I'm no C# savvy. I'm looking for a simpler approach to avoid any possible headaches, I'm on a tight schedule here :S For now, I'm just going to leave it as it is, the simple presence of the activity indicator, even though not spinning, should be enough to let the user know something's happening, I hope... – Tharkius Jul 25 '16 at 15:35
  • Maybe making sure the data loading happens asynchronously (aka don't do it in constructor or navigated to) will be enough, but basically, yeah, thats what it means. Performance with complex data is not a thing for tight schedules. :P – Kai Brummund Jul 25 '16 at 16:18
  • This might helps you, this solved mine. 'https://forums.xamarin.com/discussion/50338/consistency-error-you-are-calling-a-uikit-method-that-can-only-be-invoked-from-the-ui-thread' – Riyas Jul 05 '18 at 06:00

3 Answers3

3

You should try wrapping your Page Navigation in something like this:

Device.BeginInvokeOnMainThread (async() => {
await Navigation.PushAsync(new SchedulePage());
});

This allows the process to start and keeps your UI elements responsive.

Ravi L
  • 1,142
  • 9
  • 17
  • Thanks for the suggestion, I'll try it out when I can, then tell you about it. – Tharkius Jul 25 '16 at 22:46
  • 1
    Unfortunately it didn't solve the problem, I still get the activity indicator's animation halted. Thanks for your time anyway ;) – Tharkius Jul 26 '16 at 09:05
2

yesterday I was investigating this case because I have an ActivityIndicator that doesn't show when I activated it before a PushAsync. I saw here and stackoverflow and all solutions points to run the PushAsync inside a Device.BeginInvokeOnMainThread inside a Task.Run, I tried it but nothing change.

Then I realized that all I needed was another async task that gaves ActivityIndicator time before PushAsync ran. So here is the code works in my project:

public partial class MyActivities : ContentPage
{
    private bool _isLoading;

    public bool IsLoading //Binded to ActivityIndicator
    {
        get { return _isLoading; }
        private set
        {
            _isLoading = value;
            OnPropertyChanged("IsLoading");
        }
    }

    public MyActivities()
    {
        BindingContext = this;
        InitializeComponent();

        btnNavitage.Clicked += async (s,a) => 
        {
            IsLoading = true;
            var animateEnd = await aiControl.FadeTo(1d); //aiControl is the ActivityIndicator, animate it do the trick
            try
            {
                await Navigation.PushAsync(new CheckInForm()); //when the code reach the PushAsync, ActivityIndicator is already running
            }
            finally
            {
                IsLoading = false;
            }
        }
    }
}
jotade
  • 438
  • 2
  • 5
  • var animateEnd = await aiControl.FadeTo(1d); /// this line will make the indicator play but will not execute the PushAsync method until it's finished. – Ahmed Salah Nov 16 '18 at 10:26
  • @AhmedSalah `FadeTo` has a param to indicate duration. I tried `FadeTo` without `await` but it doesn't show the indicator. – jotade Nov 19 '18 at 20:23
0

//As a follow-up to jotade's
//No need to set another property, just set IsRunning to True in XAML and do the follow

        -- code remove for brevity

        ActivityIndicator.IsVisible = true;

        await Task.Delay(500); 
        try
        {
            await Navigation.PushAsync(new TransactionCalculationPage(), true);
        }
        finally
        {
            ActivityIndicator.IsVisible = false;
        }   
  • Your answer is poorly formatted, please update formatting and read the SO guidelines before posting. – sparkitny Jan 22 '18 at 00:23
  • I ended up doing something similar in my code base. But I used `Task.Yield()` instead of `Task.Delay()` – Mitkins Feb 08 '21 at 23:32
  • Ah, but then I ran the code on a slower device (a Galaxy Tab A). The half second delay allowed the ActivityIndicator to animate for a short duration before it stalled! – Mitkins Feb 09 '21 at 23:04