Search code examples
androidandroid-fragmentsandroid-collapsingtoolbarlayoutandroid-architecture-navigationandroid-statusbar

setting SYSTEM_UI_FLAG_LIGHT_STATUS_BAR makes next fragment blank and collapsing toolbar unstable on api <= 27


I use NavHostFragment with BottomNavigationView with 5 tabs and fitsSystemWindows="true".

Description:

App has different app bar design for each fragment. For example, in 1st fragment app bar contains CollapsingToolbarLayout with background image which is drawn under status bar, and in 2nd fragment app bar contains only Toolbar and nothing is drawn under status bar. But to make UI not jumpy I just set all fragments layout to use fitsSystemWindows="true".

In first case, status bar background needs to be transparent to make image visible and it's icon's color should be white. But in the 2nd fragment app bar and status bar are white and icon's color should be dark.

I was using these two methods below to change status bar settings when switching between fragments.

private fun setStatusBarBackgroundColor(colorId: Int) {
    window.statusBarColor = ContextCompat.getColor(this, colorId)
}

@RequiresApi(Build.VERSION_CODES.M)
private fun setStatusBarIconsColor(isDark: Boolean) {   // this is causing the issue
    val oldFlags = window.decorView.systemUiVisibility
    var newFlags = oldFlags
    newFlags = if (isDark) {
        newFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    } else {
        newFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
    }
    if (newFlags != oldFlags) window.decorView.systemUiVisibility = newFlags
}

Problem:

However, on android Oreo and lower (23 >= API <= 27) I noticed that when navigating between fragments any of them can become blank at any time. After that, all of the next destinations (even navigating back) are blank as well. Only BottomNavigationView is visible and content of NavHostFragment is just blank. But in the logcat I see that fragments are created and data fetching is done, etc. No errors logs though

Prior to blank fragments if I swipe down to activate SwipeRefreshLayout and while it is refreshing I try to scroll, CollapsingToolbarLayout starts to behave very strangely. Scroll events won't be passed to RecyclerView until CollapsingToolbarLayout fully collapsed. Then RecyclerView will start to scroll but its content won't be drawn on all available vertical space but be hidden on top as if CollapsingToolbarLayout was expanded.

Removing invocation of method with SYSTEM_UI_FLAG_LIGHT_STATUS_BAR solves the problem. But I need to toggle the color of status bar icons.


Solution

  • Android team added new API to make working with Window APIs easier. I'm not sure which version of core library has them but I believe starting from 1.5.0 these APIs are available. I'm using androidx.core:core-ktx:1.6.0.

    Now setAppearanceLightStatusBars(boolean isLight) from InsetsController class allows to easily change the appearance of icons in status bar in runtime.

    Solution:

    WindowCompat.getInsetsController(window, window.decorView)?.isAppearanceLightStatusBars = isLightStatusBar
    

    NOTE: on API 30 and later, calling this method has NO Effect if app has specified <item name="android:windowLightStatusBar">true/false</item> in the AppTheme in styles.xml. To be able to toggle status bar icons' color in runtime on API 30+, remove android:windowLightStatusBar from AppTheme

    Under the hood, this method has 2 different implementations depending on Android OS version.

    Internal implementation for API 23-29:

    // internal method
    public void setAppearanceLightStatusBars(boolean isLight) {
        if (isLight) {
            unsetWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            setWindowFlag(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            setSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        } else {
            unsetSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
    }
    

    Internal implementation for for API 30 and later:

    // internal method
    public void setAppearanceLightStatusBars(boolean isLight) {
        if (isLight) {
            mInsetsController.setSystemBarsAppearance(
                WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
                WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
        } else {
            mInsetsController.setSystemBarsAppearance(
                0,
                WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
        }
    }