Rebounder Tech Blog

Written by the people who actually run these systems in production.

insetsController Is null Before setContentView

Published About 3 min readBy the Rebounder engineering team — the people who operate these systems

This article may contain affiliate links. Its content is not affected by advertising.

In short

window.insetsController returns null before setContentView() because the DecorView is not yet resolved, so calling hide() at that point never applies immersive mode.

Conclusion

window.insetsController returns null before setContentView() is called. The DecorView is not resolved yet. Written null-safely, it does not crash either — instead the immersive hide() never runs and the code finishes as if nothing had happened.

Symptom

A signage device needed one Activity permanently in fullscreen. Its onCreate() looked like this.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    window.addFlags(
        WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
            WindowManager.LayoutParams.FLAG_FULLSCREEN or
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    )

    // Fullscreen immersive
    enterImmersiveMode()

    val root = FrameLayout(this).apply { setBackgroundColor(Color.BLACK) }
    // ... addView a WebView and others to root ...

    setContentView(root)
    // ...
}

enterImmersiveMode() is called before setContentView(root). It builds, and it does not crash at runtime. But on the device, the status bar and navigation bar do not go away.

Cause

Here is what enterImmersiveMode() did on the Android 11 / API 30 and later branch.

private fun enterImmersiveMode() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.setDecorFitsSystemWindows(false)
        window.insetsController?.let {
            it.hide(android.view.WindowInsets.Type.systemBars())
            it.systemBarsBehavior =
                android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
    } else {
        // ...
    }
}

window.insetsController is only available once the DecorView is resolved. The DecorView is resolved when setContentView() runs, so accessing insetsController before that returns null.

The trap is that the call is written null-safely as ?.let { ... }. When it is null, the whole block — including it.hide(...) — is skipped without an exception and without a log line. “Called but had no effect” never shows up as a crash, so nothing surfaces until someone looks at the screen.

The fix

Move the enterImmersiveMode() call after setContentView(root).

setContentView(root)

// Immersive mode must come after setContentView: insetsController is only
// valid once the DecorView is resolved.
enterImmersiveMode()

That alone fixes it, but the insetsController calls were also wrapped in try-catch so that a null or an exception does not stop the rest of the initialisation.

private fun enterImmersiveMode() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        try {
            window.setDecorFitsSystemWindows(false)
        } catch (_: Throwable) {}
        window.insetsController?.let {
            try {
                it.hide(android.view.WindowInsets.Type.systemBars())
                it.systemBarsBehavior =
                    android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
            } catch (_: Throwable) {}
        }
    } else {
        // ...
    }
}

Preventing a repeat

Fixing the order alone means the same trap waits for whoever next adds initialisation before setContentView(). Both setDecorFitsSystemWindows and the insetsController calls are now wrapped in try-catch, so that even with the order wrong, only immersive mode fails and the rest of startup continues.

A null-safe ?.let blurs the difference between “does not crash” and “works as intended”. When calling an API that depends on the DecorView, the safe-either-way form has to go in at the same time.

Frequently asked questions

Q1What happens if I call insetsController before setContentView?

window.insetsController returns null. If the call is written null-safely as ?.let { ... }, no exception is thrown either, so hide() simply never runs and the whole immersive setup is skipped in silence.

Q2Why is this so hard to notice when nothing errors?

Wrapping the insetsController call in ?.let means the entire block is skipped when it is null, with no log output. Because it does not crash, you only find out by looking at the screen and seeing the status bar still there.

Q3Does this also affect Android 10 and earlier?

No. This is specific to the window.insetsController path used from Build.VERSION_CODES.R (API 30) onwards. Below R the code writes systemUiVisibility directly, which does not depend on when the DecorView is resolved.

Q4Is fixing the call order enough to stop it recurring?

Order alone leaves the same trap for the next person. Both setDecorFitsSystemWindows and the insetsController calls were also wrapped in try-catch so that a null or an exception fails only immersive mode, without stopping the rest of the initialisation.

Environment verified

  • compileSdk 34 / targetSdk 34 / minSdk 26 (Kotlin, AppCompatActivity)
  • Occurred and fixed on 2026-05-30

What this article is based on

  • Kotlin file lines 78-119commit 9f413fa
  • Kotlin file lines 192-199commit 9f413fa
  • Kotlin file lines 78-119commit f11de55
  • Kotlin file lines 192-203commit f11de55

Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.