Question

Replace systemUiVisibility with WindowInsetsController in Android Kotlin

kotlinandroid

Question

After updating an Android project to target API level 30, the View.systemUiVisibility property is deprecated. The Kotlin code below is equivalent to Java's setSystemUiVisibility():

copyPlayerView.systemUiVisibility = (
    View.SYSTEM_UI_FLAG_LOW_PROFILE or
        View.SYSTEM_UI_FLAG_FULLSCREEN or
        View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
)

What is the stable replacement for this code? Android documentation recommends WindowInsetsController, but how should it be used to hide the system bars and enable immersive behavior?

Short Answer

You will learn why systemUiVisibility was deprecated, how its flags map to the newer Window Insets APIs, and how to use WindowInsetsControllerCompat in Kotlin for an immersive Android screen that works across Android versions.

Concept

View.systemUiVisibility used one integer containing many bit flags to control the status bar and navigation bar. It could hide bars, request fullscreen layout, and configure immersive mode.

Android now separates those responsibilities through the Window Insets APIs:

  • WindowCompat.setDecorFitsSystemWindows() controls whether app content is laid out around system bars or can draw edge to edge.
  • WindowInsetsControllerCompat.hide() hides selected system bars.
  • WindowInsetsControllerCompat.systemBarsBehavior controls how hidden bars reappear after user interaction.

This separation is useful because system bars are part of the window, not just one view. The newer API also works better with display cutouts, gesture navigation, keyboard insets, and modern edge-to-edge layouts.

For most applications, prefer the AndroidX compatibility version, WindowInsetsControllerCompat. It provides one API for modern Android versions and older supported versions. The platform WindowInsetsController is available directly from API level 30.

Mental Model

Think of the app window as a room and the system bars as movable curtains.

The old systemUiVisibility API was a single remote with many buttons pressed at once. You combined flags into one number and assigned it to a view.

The new API gives each job its own control:

  • setDecorFitsSystemWindows(false) decides whether your furniture can extend behind the curtains.
  • hide(...) closes the status and navigation-bar curtains.
  • systemBarsBehavior decides what happens when a user swipes near a hidden curtain.

This makes it clearer which setting controls layout, visibility, and user interaction.

Take Quiz

Syntax and Examples

For a media player or fullscreen screen, use AndroidX Core's window-insets APIs.

import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat

fun enableImmersiveMode() {
    WindowCompat.setDecorFitsSystemWindows(window, false)

    WindowInsetsControllerCompat(window, window.decorView).apply {
        hide(
            WindowInsetsCompat.Type.statusBars() or
                WindowInsetsCompat.Type.navigationBars()
        )
        systemBarsBehavior =
            WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
    }
}

Call this from an Activity, where window is available. If the code is inside a Fragment, use the hosting activity's window:

val activityWindow = requireActivity().window

WindowCompat.setDecorFitsSystemWindows(activityWindow, false)
WindowInsetsControllerCompat(activityWindow, activityWindow.decorView).apply {
    hide(WindowInsetsCompat.Type.systemBars())
    systemBarsBehavior =
        WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}

Type.systemBars() is a shortcut for the status bar and navigation bar. The behavior constant is the modern equivalent of the important user-facing behavior of SYSTEM_UI_FLAG_IMMERSIVE_STICKY: a swipe temporarily reveals the bars, then they hide again.

Step by Step Execution

Consider this fullscreen player setup:

WindowCompat.setDecorFitsSystemWindows(window, false)

val controller = WindowInsetsControllerCompat(window, window.decorView)
controller.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller.hide(WindowInsetsCompat.Type.systemBars())

Step by step:

  1. setDecorFitsSystemWindows(window, false) tells Android that the app may draw behind system bars. This replaces layout-related flags such as SYSTEM_UI_FLAG_LAYOUT_STABLE and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
  2. WindowInsetsControllerCompat(...) creates a controller for the activity window.
  3. systemBarsBehavior is set before hiding the bars. A user can swipe from an edge to reveal the bars temporarily.
  4. hide(Type.systemBars()) requests that Android hide both the status bar and navigation bar.
  5. The user can still access system controls with the configured gesture. Apps must not permanently prevent users from accessing navigation controls.

Real World Use Cases

Common uses of Window Insets controllers include:

  • Video players: hide controls and system bars while a video plays fullscreen.
  • Photo viewers: provide an unobstructed image preview, then reveal controls on touch.
  • Games: draw across the whole display while allowing transient system-bar access.
  • Reading screens: offer a distraction-free reading mode.
  • Camera previews: allow preview content to extend behind bars while carefully placing interactive controls outside unsafe areas.
  • Immersive onboarding: temporarily present an animation or experience without persistent system chrome.

For ordinary app screens, hiding system bars is usually unnecessary. Edge-to-edge layout with visible, readable bars is often the better choice.

Take Quiz

Real Codebase Usage

In production code, keep window configuration in a small, reusable function rather than repeating it in every screen.

import android.app.Activity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat

fun Activity.enterFullscreenPlayer() {
    WindowCompat.setDecorFitsSystemWindows(window, false)

    WindowInsetsControllerCompat(window, window.decorView).apply {
        systemBarsBehavior =
            WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        hide(WindowInsetsCompat.Type.systemBars())
    }
}

fun Activity.exitFullscreenPlayer() {
    WindowInsetsControllerCompat(window, window.decorView).show(
        WindowInsetsCompat.Type.systemBars()
    )
}

A player screen may call enterFullscreenPlayer() when playback enters fullscreen and exitFullscreenPlayer() when the user exits the player.

Also handle insets for controls that must not be covered by gesture areas or bars. For example, a bottom playback-control bar can add bottom padding based on the navigation-bar inset:

import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

ViewCompat.setOnApplyWindowInsetsListener(playbackControls) { view, insets ->
    val bars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
    view.setPadding(
        view.paddingLeft,
        view.paddingTop,
        view.paddingRight,
        bars.bottom
    )
    insets
}

This is especially important when content draws edge to edge.

Common Mistakes

Using the platform controller without considering older Android versions

This only works on API level 30 and later:

// Requires API 30+
window.insetsController?.hide(WindowInsets.Type.systemBars())

Avoid version checks in most apps by using WindowInsetsControllerCompat instead.

Hiding bars but not enabling edge-to-edge layout

controller.hide(WindowInsetsCompat.Type.systemBars())

This hides bars, but it does not explicitly configure whether content can draw behind them. For immersive content, usually also use:

WindowCompat.setDecorFitsSystemWindows(window, false)

Treating old flags as a one-to-one replacement

Not every old flag has an exact equivalent:

  • FULLSCREEN becomes hiding statusBars().
  • HIDE_NAVIGATION becomes hiding navigationBars().
  • Layout flags become setDecorFitsSystemWindows(false).
  • IMMERSIVE_STICKY becomes a system-bars behavior.
  • LOW_PROFILE is not normally needed when the bars are hidden.

Forgetting to restore bars

If fullscreen is temporary, call show() when leaving it:

controller.show(WindowInsetsCompat.Type.systemBars())

Assuming the app can lock users out of system navigation

Android intentionally keeps system navigation accessible. Immersive behavior requests hidden bars, but the user can reveal them with system gestures.

Using a child view as if it owns the window

systemUiVisibility was assigned to a view, but the modern controller controls a window. Create it with the activity window and its decor view:

WindowInsetsControllerCompat(window, window.decorView)

Comparisons

Old systemUiVisibility flagModern approachPurpose
SYSTEM_UI_FLAG_FULLSCREENhide(Type.statusBars())Hide the status bar.
SYSTEM_UI_FLAG_HIDE_NAVIGATIONhide(Type.navigationBars())Hide the navigation bar.
Both flags togetherhide(Type.systemBars())Hide both system bars.
SYSTEM_UI_FLAG_LAYOUT_STABLEWindowCompat.setDecorFitsSystemWindows(window, false)Use an edge-to-edge layout strategy.
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONWindowCompat.setDecorFitsSystemWindows(window, false)Let content extend behind navigation areas.
SYSTEM_UI_FLAG_IMMERSIVE_STICKYBEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPEReveal bars temporarily after an edge swipe.
SYSTEM_UI_FLAG_LOW_PROFILEUsually omit it; on API 30+ use platform appearance APIs if truly requiredDim system-bar icons instead of hiding bars.
APIBest choice
App supports multiple Android versionsWindowInsetsControllerCompat and WindowCompat from AndroidX Core.
App runs only on API 30+Platform WindowInsetsController is available, but AndroidX is still often simpler.
Screen only needs edge-to-edge contentUse setDecorFitsSystemWindows(false) and apply insets to important controls; do not necessarily hide bars.
Take Quiz

Cheat Sheet

import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat

val controller = WindowInsetsControllerCompat(window, window.decorView)

// Allow content to draw behind system bars.
WindowCompat.setDecorFitsSystemWindows(window, false)

// Hide both status and navigation bars.
controller.hide(WindowInsetsCompat.Type.systemBars())

// Hide only one bar.
controller.hide(WindowInsetsCompat.Type.statusBars())
controller.hide(WindowInsetsCompat.Type.navigationBars())

// Let an edge swipe reveal hidden bars temporarily.
controller.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

// Restore bars.
controller.show(WindowInsetsCompat.Type.systemBars())

Quick rules:

  • Prefer WindowInsetsControllerCompat for compatibility.
  • Configure the window, not only a child view.
  • hide() controls visibility; setDecorFitsSystemWindows(false) controls layout.
  • Apply insets as padding or margins to buttons and controls that must remain reachable.
  • Restore system bars when fullscreen mode ends.

FAQ

Why is systemUiVisibility deprecated?

It combines several unrelated window and system-bar behaviors in one set of view flags. Window Insets APIs provide clearer controls for layout, visibility, and behavior.

What replaces SYSTEM_UI_FLAG_FULLSCREEN in Kotlin?

Hide the status bar:

controller.hide(WindowInsetsCompat.Type.statusBars())

What replaces SYSTEM_UI_FLAG_HIDE_NAVIGATION?

Hide the navigation bar:

controller.hide(WindowInsetsCompat.Type.navigationBars())

What replaces SYSTEM_UI_FLAG_IMMERSIVE_STICKY?

Use:

controller.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

Does WindowInsetsControllerCompat work below Android 11?

Yes. It is the AndroidX compatibility API designed to provide a consistent interface across supported Android versions.

Is SYSTEM_UI_FLAG_LOW_PROFILE required for immersive mode?

No. Low-profile mode dims system-bar icons, while immersive mode normally hides the bars. If the bars are hidden, low-profile appearance has no visible benefit.

How do I show the system bars again?

Call:

controller.show(WindowInsetsCompat.Type.systemBars())

Should every screen hide system bars?

No. Reserve fullscreen or immersive behavior for cases such as video playback, games, camera previews, and photo viewers. Standard app screens should generally preserve easy access to system controls.

Take Quiz

Mini Project

Description

Build a small fullscreen media-player mode for an Android activity. It demonstrates entering immersive mode for playback and restoring normal system bars when the user leaves fullscreen.

Goal

Create buttons that hide and show the status and navigation bars using WindowInsetsControllerCompat.

Requirements

Use WindowCompat.setDecorFitsSystemWindows(window, false) when fullscreen mode starts.

Hide both the status bar and navigation bar.

Use transient swipe behavior so users can reveal system bars temporarily.

Provide a way to exit fullscreen and show the bars again.

Use AndroidX compatibility APIs rather than deprecated systemUiVisibility.

Take Quiz

Keep learning

Related questions

View more