Xamarin.forms: OnOptionsItemSelected() not called after a configuration/orientation change.

Created on 20 Dec 2018  路  9Comments  路  Source: xamarin/Xamarin.Forms

Description

In my application I have to intercept the software back button click and it works fine as long as I don't change the orientation. After changing the orientation from portait to landscape the method OnOptionsItemSelected() is never called again.

[Activity(Label = "TestApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
     protected override void OnCreate(Bundle savedInstanceState)
     {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;
            base.OnCreate(savedInstanceState);

            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new MyApp());
     }
     protected override void OnPostCreate(Bundle savedInstanceState)
     {
            base.OnPostCreate(savedInstanceState);
            Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);
      }

      public override bool OnOptionsItemSelected(IMenuItem item)
      {
            return base.OnOptionsItemSelected(item);
      }
}

styles.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>

  <style name="MainTheme" parent="MainTheme.Base">
  </style>
  <!-- Base theme applied no matter what API -->
  <style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
    <!--If you are using revision 22.1 please use just windowNoTitle. Without android:-->
    <item name="windowNoTitle">true</item>
    <!--We will be using the toolbar so no need to show ActionBar-->
    <item name="windowActionBar">true</item>
    <!-- Set theme colors from http://www.google.com/design/spec/style/color.html#color-color-palette -->
    <!-- colorPrimary is used for the default action bar background -->
    <item name="colorPrimary">#2196F3</item>
    <!-- colorPrimaryDark is used for the status bar -->
    <item name="colorPrimaryDark">#1976D2</item>
    <!-- colorAccent is used as the default value for colorControlActivated
         which is used to tint widgets -->
    <item name="colorAccent">#FF4081</item>
    <!-- You can also set colorControlNormal, colorControlActivated
         colorControlHighlight and colorSwitchThumbNormal. -->
    <item name="windowActionModeOverlay">true</item>

    <item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
  </style>

  <style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">#FF4081</item>
  </style>
</resources>

toolbar.axml

<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

Steps to Reproduce

  1. Start the app
  2. Change orientation
  3. OnOptionsItemSelected() not called an backbutton

Expected Behavior

Actual Behavior

Basic Information

Microsoft Visual Studio Enterprise 2017
Version 15.9.4

Xamarin 4.12.3.77
Xamarin.Android SDK 9.1.4.2

  • Version with issue:
  • IDE:
  • Platform Target Frameworks:

    • Android: 7.0

  • Nuget Packages:
    Xamarin.Forms 3.4.0.1008975
    Xamarin.Android.Support.... 28.0.0
  • Affected Devices: Samsung Galaxy S5

Screenshots

Reproduction Link

6 help wanted inactive Android bug up-for-grabs

Most helpful comment

In MainActivity

protected override void OnPostCreate(Bundle savedInstanceState)
        {
            base.OnPostCreate(savedInstanceState);
            SetActionBar();
        }

protected void SetActionBar()
        {
            AndroidHelper.contextActivity = this;
            Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            toolbar.SetTitleTextColor(Android.Graphics.Color.Rgb(46, 93, 173));
            toolbar.SetSubtitleTextColor(Android.Graphics.Color.Rgb(46, 93, 173));
            AndroidHelper.CurrentApp = App.Current;
            AndroidHelper.ToolBarId = Resource.Id.toolbar;
            AndroidHelper.OnClickListener = new AndroidHelper.BackButtonClickListener();
            toolbar.SetNavigationOnClickListener(AndroidHelper.OnClickListener);
        }

NavigationPageRenderer:

public class TdNavigationPageRenderer : NavigationPageRenderer
    {
        enum TdOrientation
        {
            None = 0,
            Portait = 1,
            Landscape = 2,
        }

        TdOrientation orientation;

        public TdNavigationPageRenderer(Context context) : base(context)
        {
            orientation = TdOrientation.None;
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);


            if (orientation != TdOrientation.None)
            {
                TdOrientation newOrientation = r > b ? TdOrientation.Landscape : TdOrientation.Portait;
                if (newOrientation != orientation)
                {
                    if (AndroidHelper.contextActivity != null)
                    {
                        var that = (AndroidHelper.contextActivity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity);
                        var toolbar = that.FindViewById<Android.Support.V7.Widget.Toolbar>(AndroidHelper.ToolBarId);
                        toolbar.SetNavigationOnClickListener(AndroidHelper.OnClickListener);
                    }
                }
            }

            orientation = r > b ? TdOrientation.Landscape : TdOrientation.Portait;
     }
}

AndroidHelper:

public static class AndroidHelper
{


    public static Activity contextActivity { get; set; }
    public static BackButtonClickListener OnClickListener { get; set; }
    public static Xamarin.Forms.Application CurrentApp { get; set; }
    public static int ToolBarId { get; set; }



    public class BackButtonClickListener : Java.Lang.Object, IOnClickListener
    {
        public void OnClick(Android.Views.View view)
        {

            if (CurrentApp != null)
            {
                if (CurrentApp.MainPage.Navigation.NavigationStack.Count > 0)
                {
                    int index = CurrentApp.MainPage.Navigation.NavigationStack.Count - 1;

                    if (CurrentApp.MainPage.Navigation.NavigationStack[index] is TdContentPage)
                    {
                        var currentPage = CurrentApp.MainPage.Navigation.NavigationStack[index] as TdContentPage;

                        if (currentPage.EnableBackButtonOverride)
                        {
                            currentPage?.BackButtonAction.Invoke();
                        }
                        else
                        {
                            currentPage.UnSubscribe();
                            CurrentApp.MainPage.Navigation.PopAsync();
                        }
                    }
                    else
                        CurrentApp.MainPage.Navigation.PopAsync();
                }
                else
                {
                    if (CurrentApp.MainPage.Navigation.ModalStack.Count > 0)
                    {
                        CurrentApp.MainPage.Navigation.PopModalAsync();
                    }
                }
            }
        }
    }
}

All 9 comments

Can you please attached a zipped up copy of your reproduction case?

@moctechno

if you check the PR here
https://github.com/xamarin/Xamarin.Forms/pull/4893

You'll see why rotating breaks.

I need to consult with the team a bit more to see if the behavior is intentional.
Basically when you rotate the device it causes this code to run again

https://github.com/xamarin/Xamarin.Forms/blob/ceb25378059c35fcec55e18bbbbe9c58d71b01e6/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs#L744-L759

Which now causes a different toolbar to show up. So you would need to detect the rotation and then rerun the code that calls SetSupportActionBar

When I do this my toolbaritems are removed from the current page.
I detect the rotation in a custom navigationpage renderer and call the SetSupportActionBar. After this the OnOptionsItemSelected is always called, but I lost my toobaritems.

@moctechno thank you for the followup

The more I dive into this the more I realize this isn't as easy as I had hoped it would be and it's going to require a better more fleshed out solution.

For your case one thing you could try is setting up a handler for the toolbar opposed to calling SetSupportActionBar

So call
toolbar.SetNavigationOnClickListener(someListener)
Then inside that listener is where you could then just trigger a PopAsync from the navigation page and then fire the code in OnOptionsItemSelected

I would need to play with that idea a bit more though to see if I'm overlooking something

Thank you for your quick reply.
It works.
I call SetNavigationOnClickListener() everytime a new toolbar is set.

@moctechno I have just stumbled upon this. Would you be able to direct me to some sample code to get this workaround to work? I am currently running into this issue and wouldn't mind using this until a permanent fix is in place.

In MainActivity

protected override void OnPostCreate(Bundle savedInstanceState)
        {
            base.OnPostCreate(savedInstanceState);
            SetActionBar();
        }

protected void SetActionBar()
        {
            AndroidHelper.contextActivity = this;
            Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            toolbar.SetTitleTextColor(Android.Graphics.Color.Rgb(46, 93, 173));
            toolbar.SetSubtitleTextColor(Android.Graphics.Color.Rgb(46, 93, 173));
            AndroidHelper.CurrentApp = App.Current;
            AndroidHelper.ToolBarId = Resource.Id.toolbar;
            AndroidHelper.OnClickListener = new AndroidHelper.BackButtonClickListener();
            toolbar.SetNavigationOnClickListener(AndroidHelper.OnClickListener);
        }

NavigationPageRenderer:

public class TdNavigationPageRenderer : NavigationPageRenderer
    {
        enum TdOrientation
        {
            None = 0,
            Portait = 1,
            Landscape = 2,
        }

        TdOrientation orientation;

        public TdNavigationPageRenderer(Context context) : base(context)
        {
            orientation = TdOrientation.None;
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);


            if (orientation != TdOrientation.None)
            {
                TdOrientation newOrientation = r > b ? TdOrientation.Landscape : TdOrientation.Portait;
                if (newOrientation != orientation)
                {
                    if (AndroidHelper.contextActivity != null)
                    {
                        var that = (AndroidHelper.contextActivity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity);
                        var toolbar = that.FindViewById<Android.Support.V7.Widget.Toolbar>(AndroidHelper.ToolBarId);
                        toolbar.SetNavigationOnClickListener(AndroidHelper.OnClickListener);
                    }
                }
            }

            orientation = r > b ? TdOrientation.Landscape : TdOrientation.Portait;
     }
}

AndroidHelper:

public static class AndroidHelper
{


    public static Activity contextActivity { get; set; }
    public static BackButtonClickListener OnClickListener { get; set; }
    public static Xamarin.Forms.Application CurrentApp { get; set; }
    public static int ToolBarId { get; set; }



    public class BackButtonClickListener : Java.Lang.Object, IOnClickListener
    {
        public void OnClick(Android.Views.View view)
        {

            if (CurrentApp != null)
            {
                if (CurrentApp.MainPage.Navigation.NavigationStack.Count > 0)
                {
                    int index = CurrentApp.MainPage.Navigation.NavigationStack.Count - 1;

                    if (CurrentApp.MainPage.Navigation.NavigationStack[index] is TdContentPage)
                    {
                        var currentPage = CurrentApp.MainPage.Navigation.NavigationStack[index] as TdContentPage;

                        if (currentPage.EnableBackButtonOverride)
                        {
                            currentPage?.BackButtonAction.Invoke();
                        }
                        else
                        {
                            currentPage.UnSubscribe();
                            CurrentApp.MainPage.Navigation.PopAsync();
                        }
                    }
                    else
                        CurrentApp.MainPage.Navigation.PopAsync();
                }
                else
                {
                    if (CurrentApp.MainPage.Navigation.ModalStack.Count > 0)
                    {
                        CurrentApp.MainPage.Navigation.PopModalAsync();
                    }
                }
            }
        }
    }
}

@moctechno - Wow, thank you for the prompt reply and thorough example!

Was this page helpful?
0 / 5 - 0 ratings