ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-17Intermediate

Android Theme Switch White Screen: Replacing Activity.recreate() with AppRestarter

A real fix for the white screen that appears when switching themes on Android. Covers why Activity.recreate() causes the issue, how AppRestarter.safeRestart() eliminates it, and a ModalGate pattern to prevent dialog conflicts after restart.

android28themeapprestarteractivityapp-dev49ui

Every time a user tapped the theme toggle in Beautiful HD Wallpapers, there was a brief white flash before the new theme appeared. On high-end devices it was barely noticeable. On older mid-range phones running Android 7–9, it was clearly visible — and multiple user reviews mentioned it.

I knew the culprit was Activity.recreate(). What I didn't fully understand was why it caused this, and whether there was a clean way to fix it without major refactoring. I ran the problem by Antigravity, and the explanation and solution it returned were both clear and immediately applicable.

Why Activity.recreate() Causes a White Screen

The standard approach for applying a dynamic theme change in Android is to call recreate() after setting the new night mode:

// Common but problematic for low-end devices
fun applyTheme(isDark: Boolean) {
    AppCompatDelegate.setDefaultNightMode(
        if (isDark) AppCompatDelegate.MODE_NIGHT_YES
        else AppCompatDelegate.MODE_NIGHT_NO
    )
    activity.recreate() // ← the problem
}

recreate() destroys and rebuilds the Activity using the system's standard transition mechanism. Between the old Activity finishing and the new one rendering, there's a brief moment where the window is empty or defaults to the app's background — which on many devices is white. Fast processors complete this before a single frame renders. Slower ones don't.

What Antigravity Diagnosed

I passed the code and context to Antigravity:

Theme switching causes a white flash on Android 7–9 mid-range devices.
Implementation: setDefaultNightMode() followed by recreate().
High-end devices don't reproduce it.

Explain why this happens and what the recommended alternative is.

The response: "recreate() triggers the standard Activity transition animation, which includes a brief window where neither the old nor new Activity is fully rendered. The standard fix is to bypass recreate() entirely — launch the Activity via an explicit Intent with FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_NEW_TASK, then call overridePendingTransition(0, 0) to disable the enter/exit animation. This removes the gap between the two Activity instances."

The AppRestarter Implementation

Based on that guidance, I extracted the logic into a dedicated object:

object AppRestarter {
    /**
     * Safe theme-switch restart that avoids the white screen
     * caused by Activity.recreate().
     */
    fun safeRestart(activity: Activity) {
        val intent = activity.intent.apply {
            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        activity.startActivity(intent)
        activity.finish()
 
        // Disable transition animation — this is the key line
        activity.overridePendingTransition(0, 0)
    }
}

Calling it is straightforward:

fun applyTheme(isDark: Boolean) {
    AppCompatDelegate.setDefaultNightMode(
        if (isDark) AppCompatDelegate.MODE_NIGHT_YES
        else AppCompatDelegate.MODE_NIGHT_NO
    )
    AppRestarter.safeRestart(this)
}

overridePendingTransition(0, 0) suppresses both the enter animation (first argument) and exit animation (second argument). Without the animation gap, there's no visible moment when the window is empty.

Choosing the Right Intent Flags

The combination of FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_NEW_TASK is deliberate.

FLAG_ACTIVITY_CLEAR_TOP alone can leave the back stack in an inconsistent state when multiple Activities are stacked. Beautiful HD Wallpapers has a category → detail → favorites hierarchy, so after a theme switch I want to land back at the top-level screen cleanly. Adding FLAG_ACTIVITY_NEW_TASK rebuilds the task explicitly, which produces more predictable behavior.

If your app uses a single-Activity architecture with NavHostFragment, this approach needs adjustment. In that case, re-triggering a full recompose via the NavController or a global state observer is typically more appropriate than relaunching the Activity.

The ModalGate Pattern for Post-Restart Conflicts

After deploying safeRestart, a secondary issue surfaced: occasionally, a paywall dialog and a review prompt appeared simultaneously right after the theme switch.

The cause is that Activity recreation triggers multiple initialization paths at once, each potentially calling dialog.show() without knowing the others exist. The fix is a centralized gate:

object ModalGate {
    private var isShowing = false
 
    /**
     * Request permission to show a modal.
     * Returns false if another modal is already visible.
     */
    fun requestShow(show: () -> Unit): Boolean {
        if (isShowing) return false
        isShowing = true
        show()
        return true
    }
 
    fun dismiss() {
        isShowing = false
    }
}

Usage in each dialog:

// PaywallDialog
ModalGate.requestShow {
    PaywallDialog(context).show()
}
 
// ReviewInductionDialog
ModalGate.requestShow {
    ReviewInductionDialog(context).show()
}

Only one dialog can show at a time. The second call returns false and does nothing. When the user dismisses the active dialog, ModalGate.dismiss() releases the lock and the next dialog can show.

Results After v2.1.0 Staged Rollout

The fix shipped in v2.1.0 with a staged rollout: 5% → 25% → 50% → 100%. After full rollout, user reviews mentioning the white screen stopped completely.

I've been building apps independently since 2013. Bugs that don't reproduce on your own devices but consistently appear on users' older hardware are among the most frustrating to track down. The white screen issue was exactly that kind — subtle, intermittent, and device-dependent.

What Antigravity added wasn't just a code fix but a clear explanation of why the mechanism failed. Understanding overridePendingTransition(0, 0) as the solution would have taken me longer to arrive at through documentation alone, since the mental model of "animation gap = visible empty window" isn't spelled out clearly in Android docs.

For a broader look at building and shipping Android apps with Antigravity, see the production Android guide.

If you're seeing the same white screen on theme switches, the change from recreate() to AppRestarter.safeRestart() takes under an hour and has a noticeable impact on user experience across a wide range of devices.

The Bigger Picture: Why Small Fixes Matter in Indie Development

I've been building apps as an indie developer since 2013. Four iOS and Android apps — Beautiful HD Wallpapers, Ukiyo-e Wallpapers, Relaxing Healing, and Law of Attraction Everyday — now exceed 50 million cumulative downloads. Getting there wasn't about shipping flashy features. It was largely about fixing the small things that users notice but rarely articulate.

A white screen on theme switch doesn't break anything. The app keeps working. But users perceive it as low quality, even if they can't name what's wrong. That perception erodes trust over time.

My grandfathers were temple carpenters (宮大工). They believed that careful handwork is itself a form of devotion. I carry that same approach into software. As Masaki Hirokawa — an artist who has exhibited work internationally and received 17 international art awards — I've found that attention to detail in creative work and in software development come from the same place.

The recreate()AppRestarter migration is the kind of fix that doesn't make a changelog dramatic. But it matters to the people who use the app every day.

Quick Reference: Migration Steps

  1. Add AppRestarter to your project
  2. Replace every recreate() call in theme-switching logic with AppRestarter.safeRestart(this)
  3. Add ModalGate if you have multiple dialogs that can appear after restart
  4. Test on an Android 7–9 emulator with repeated theme toggling

You can introduce AppRestarter first and add ModalGate later if needed — the white screen fix alone is worth the change.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-07-03
Catching Download Size Regressions Before Submission Day — A Weekly Agent Gate for AAB/IPA Size Budgets
Mediation SDKs and bundled assets quietly inflate download size. A design for size ledgers, budget gates, and agent-driven delta attribution using bundletool and App Thinning reports.
App Dev2026-06-21
The Back Button Showed an Interstitial Sometimes, Not Others — Rewriting Nested ifs Into a List of Independent Guards
Interstitial display on back press was unstable because nested if statements hid the priority between conditions. Here is how I split it into reason-returning guards and generated tests from a decision table.
App Dev2026-05-17
When Antigravity Fixes a RecyclerView Crash but Breaks Something Else — The Defensive Copy Solution
If RecyclerView IndexOutOfBoundsExceptions keep coming back after Antigravity's fix, shared list reference is likely the cause. Here's how to prompt Antigravity for the right fix and encode the pattern in AGENTS.md.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →