Saturday, 19 January 2019

How do full screen DrawerLayouts affect adding Fragments to the backstack, when setting window SystemUiVisibility to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR?

I've encountered a Fragment-related problem in a larger project that I'm working on and I've created a minimal verifiable example to explain it:

The example setup:

The example project is an app with no action bar (the theme has Theme.AppCompat.Light.NoActionBar as its parent), with targetSdkVersion 27 and minSdkVersion 23.

The main activity is an AppCompatActivity with an android.support.v4.widget.DrawerLayout as its root layout. This DrawerLayout has android:fitsSystemWindows="true", to overlap the status bar.

Inside the DrawerLayout, there is a FrameLayout that will host an android.support.v4.app.Fragment. The fragment is added to the interface and to the backstack when a button is tapped:

FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragmentContent, fragment);
transaction.addToBackStack(fragment.getClass().getName());
transaction.commitAllowingStateLoss();

This fragment shows a button that removes the fragment by calling onBackPressed on the activity.

So far, the app's behavior is normal: the fragment is added when tapping the button on the activity layout and then it's removed when tapping the button on the fragment layout.

The problem:

I add a new button to the activity, that when tapped, sets the status bar color to white and the SystemUiVisibility for the window decor view, to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR:

Window window = getWindow();
window.setStatusBarColor(ContextCompat.getColor(this, android.R.color.white));
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

After tapping this button, the status bar is, as expected, shown with black text on a white background.

The unexpected part is that after this change, the fragment is no longer shown on the interface when I tap the button that adds it.

The fragment still appears to be placed on the backstack, since pressing the system back key doesn't immediately finish the activity, removing the fragment instead. I've confirmed this by adding log statements in the fragment's onResume and onPause methods and seeing that they are called when the fragment is added and removed from the backstack.

The investigation:

I've tried multiple workarounds and I've explored a number of possible causes.

What I've managed to find:

  • The problem only occurs for some app configurations. All of these should be true for the problem to manifest itself:

    1. The app theme has Theme.AppCompat.Light.NoActionBar as its parent.
    2. The activity has android.support.v4.widget.DrawerLayout as its root layout.
    3. The DrawerLayout has android:fitsSystemWindows="true".
    4. Both the status bar color and the SystemUiVisibility for the window decor view must be set.
    5. The SystemUiVisibility for the window decor view must be set to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.

    These may seem arbitrary, but I need them in my app to have the interface show no action bar, provide a full-screen side menu and adapt the color of the status bar to the color of each layout I'm showing.

  • I've looked into the code for DrawerLayout, as a possible source of insights into the matter. I've even created a local copy of the component, so I could comment out various sections and add log statements to trace the behavior.

    I've managed to track down the problem to DrawerLayout setting its SystemUiVisibility to SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.

    When these flags are set, the OnApplyWindowInsetsListener gets called when I set the status bar color to white and the SystemUiVisibility for the window decor view to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.

    In turn, this listener calls a setChildInsets method, that calls requestLayout() on the DrawerLayout, at the end.

    This layout request is the last received by the DrawerLayout. From this point onward, its onLayout method no longer gets called when setting the status bar color and the SystemUiVisibility for the window decor view, nor when adding a fragment. Nor the onMeasure method or the OnApplyWindowInsetsListener ever get called after this point. Fragment views are no longer shown and logging their size reveals that it stays 0.

The code:

You can download the example project.

The question(s):

What causes this, how and why? Does it have anything to do with DrawerLayout directly, or is its behavior only a consequence triggered by something else?

Why does this happen only when the app has no action bar and when SystemUiVisibility for the window decor view is set only to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR?



from How do full screen DrawerLayouts affect adding Fragments to the backstack, when setting window SystemUiVisibility to SYSTEM_UI_FLAG_LIGHT_STATUS_BAR?

No comments:

Post a Comment