2
votes

I have a multi-page (Android & iOS) Xamarin.Forms app following the MVVM pattern. From time to time the business logic may invoke an important activity, like trigger a data synchronization with some web service. I't like to show the user that there's something going on in the background. An ActivityIndicator in the Toolbar would be my ideal solution. The activity isn't related to the page in the foreground, so I don't want to put the indicator on the page, I want it somewhere outside, the Toolbar seems to be the right spot.

My first attempt was via XAML:

<ContentPage.ToolbarItems>
    <ToolbarItem>
        <ActivityIndicator IsRunning="{Binding IsRunning}" />
    </ToolbarItem>
</ContentPage.ToolbarItems>

While this (and similar approaches) compiles, it will throw an exception, seems like an ActivityIndicator may no way be a child of ToolbarItems or any Toolbar Item.

Flipping man pages I stumbled across the IsBusy property of the page class.

https://developer.xamarin.com/api/property/Xamarin.Forms.Page.IsBusy/

To try, I used this code:

public App ()
{
    InitializeComponent();
    MainPage = new App1.MainPage();
    MainPage.IsBusy = true;
}

The result is - nothing. No error, but nothing displays in the Toolbar. I found some indication in forums that this functionality was probably discarded in the newest Android versions, but nothing definite, and the docs don't say so.

How can I achieve what I want?

1

1 Answers

2
votes

Here is code that can be used in your PCL and Android projects, unfortunately I haven't written the iOS custom renderer at the moment. Anways, you'll need a custom NavigationPage in your PCL, as well as a custom NavigationPageRenderer in your Android project:

CustomNavigationPage.cs (in your PCL project):

public class CustomNavigationPage : NavigationPage
{
    public event EventHandler<EventArgs> OnShowActivityIndicator;
    public event EventHandler<EventArgs> OnHideActivityIndicator;
}

CustomNavigationPageRenderer.cs (in your Android project):

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace MyProject.Droid.CustomRenderers
{
    CustomNavigationPage page;
    Android.Support.V7.Widget.Toolbar _toolbar;
    Android.Widget.ProgressBar _progressBar;
    bool _isProgressBarCurrentlyOnToolBar = false;

    public class CustomNavigationPageRenderer : NavigationPageRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<CustomNavigationPage> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                page = (CustomNavigationPage)e.NewElement;
                page.OnShowActivityIndicator += HandleShowActivityIndicator;
                page.OnHideActivityIndicator += HandleHideActivityIndicator;
            }
        }

        private void HandleShowActivityIndicator(object sender, EventArgs e)
        {
            var progressBar = GetProgressBar();
            if (progressBar == null)
                return;

            var toolbar = GetToolbar();
            if (toolbar == null)
                return;

            Device.BeginInvokeOnMainThread(() => 
            {
                if (_isProgressBarCurrentlyOnToolBar == false)
                {
                    toolbar.AddView(progressBar);
                    _isProgressBarCurrentlyOnToolBar = true;
                }
            });
        }

        private void HandleHideActivityIndicator(object sender, EventArgs e)
        {
            var progressBar = GetProgressBar();
            if (progressBar == null)
                return;

            var toolbar = GetToolbar();
            if (toolbar == null)
                return;

            Device.BeginInvokeOnMainThread(() => 
            {
                if (_isProgressBarCurrentlyOnToolBar)
                {
                    toolbar.RemoveView(progressBar);
                    _isProgressBarCurrentlyOnToolBar = false;
                }
            });
        }

        private Android.Support.V7.Widget.Toolbar GetToolbar()
        {
            if (_toolbar == null)
            {
                for (var i = 0; i < this.ChildCount; i++)
                {
                    var child = GetChildAt(i);

                    if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
                    {
                        _toolbar = (Android.Support.V7.Widget.Toolbar)child;
                    }
                }
            }

            return _toolbar;
        }

        private Android.Widget.ProgressBar GetProgressBar()
        {
            if (_progressBar == null)
            {
                _progressBar = new Android.Widget.ProgressBar(Xamarin.Forms.Forms.Context)
                {
                    Indeterminate = true,
                    LayoutParameters = new LayoutParams(Utils.PxToDip(20), Utils.PxToDip(20))
                };
                _progressBar.SetPadding(10, 0, 0, 0);
            }

            return _progressBar;
        }

    }
}

To use it is simple, in your code behind simply invoke the event handler. ie:

public class MyPage : CustomNavigationPage
{
    public MyPage()
    {
        // show it:
        OnShowActivityIndicator?.Invoke(this, null);

        // hide it:
        OnHideActivityIndicator?.Invoke(this, null);
    }
}