ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-01Intermediate

Migrating Wallpaper Apps to Mandatory Edge-to-Edge on targetSdk 36 with Antigravity

The moment I bumped targetSdk to 36 (Android 16), the toolbar in my wallpaper preview screen slid under the status bar clock. Here is a working memo on handling the now-unavoidable edge-to-edge enforcement across several apps with Antigravity's agent.

Android 16edge-to-edgetargetSdk 36Antigravity338indie development15

I bumped targetSdk to 36, rebuilt, opened the usual wallpaper preview screen, and the top toolbar was sitting right on top of the status bar clock. The trick I had been leaning on — setting windowOptOutEdgeToEdgeEnforcement to true — no longer worked on Android 16.

I have been shipping iOS and Android apps on my own since 2014, mostly wallpaper and calming-themed apps. "50 million cumulative downloads" sounds impressive as a number, but in practice it means I run a fleet of structurally similar apps in parallel, and every OS-level behavior change forces me to fix all of them at once. The edge-to-edge enforcement was a textbook example. This article is less a step-by-step tutorial and more a record of where I let Antigravity's agent do the work and where I insisted on checking things with my own eyes.

Why "later" was not an option

On Android 15 (API 35), apps targeting SDK 35 already went edge-to-edge by default. But back then you could revert to the old behavior by setting windowOptOutEdgeToEdgeEnforcement to true, and honestly, I leaned on that opt-out to keep putting the work off.

Android 16 (API 36) changes the rules. For apps targeting SDK 36, that attribute is deprecated and disabled — you can no longer refuse edge-to-edge. To be precise about the behavior: an app targeting SDK 36 still honors the opt-out while running on an Android 15 device, but the opt-out is disabled on an Android 16 device. In other words, as user devices update, full-screen layout arrives whether I act or not.

There was a second reason I stopped postponing. A wallpaper preview screen is actually a natural fit for edge-to-edge. I want the image to reach every corner of the display, so the margin I had been reserving under the system bars was a loss, not a feature. Reframing this from "a chore I'll get to someday" to "a change that improves the experience today" was what finally got me moving.

First, remove the opt-out everywhere

The first task was deleting every windowOptOutEdgeToEdgeEnforcement that had been my escape hatch. I asked Antigravity's Agent Mode to enumerate every place the attribute was used across the repository. It lived not only in themes.xml but, in a couple of apps, in per-Activity theme overrides — spots I would have missed working from memory alone.

<!-- Before: I relied on this -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
 
<!-- After: drop the attribute entirely -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
    <!-- switch to accepting edge-to-edge as the baseline -->
</style>

While I was at it, android:statusBarColor and android:navigationBarColor are also deprecated under targetSdk 36. I abandoned the whole idea of hiding the bars by painting them a color, and switched to a policy of letting the bars stay transparent with content drawn underneath.

Rewrite each screen to be the one that receives insets

Once the opt-out is gone, content predictably slips under the system bars. This is the real work: each screen needs to receive the insets (the region the system bars occupy) and apply its own padding.

For older View-based Activities, the reliable recipe was to call enableEdgeToEdge() and then attach an inset listener to the root view.

import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
 
class PreviewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_preview)
 
        // Keep the wallpaper full-bleed; only the control UI dodges the bars.
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.toolbar)) { v, insets ->
            val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(v.paddingLeft, bars.top, v.paddingRight, v.paddingBottom)
            insets
        }
    }
}

Where I tripped: on one screen I had left fitsSystemWindows="true" in the XML and written an inset listener, so padding was applied twice and the margin actually grew. On Android 16 I found it cleanest to remove fitsSystemWindows and funnel all inset handling into a single place.

For the newer apps already on Jetpack Compose, it was more straightforward. Either pass Scaffold's innerPadding inward, or — better for a wallpaper — leave the full-screen image layer untouched and apply safeDrawingPadding() only to the control layer.

Box(Modifier.fillMaxSize()) {
    // Draw the wallpaper to every corner (no insets here)
    WallpaperImage(modifier = Modifier.fillMaxSize())
 
    // Controls dodge the system bars
    PreviewControls(
        modifier = Modifier
            .align(Alignment.TopCenter)
            .safeDrawingPadding(),
    )
}

That distinction — "don't pad the image, pad only the UI" — is, I think, the crux of edge-to-edge for a wallpaper app. Slap safeDrawingPadding() on everything and you throw away the very full-screen preview you just earned.

Where I used Antigravity, and where I didn't

Across this multi-app effort, the agent earned its keep on mechanically enumerable work: listing the opt-out attributes, locating every statusBarColor usage, and proposing candidate Activities with no inset listener. Handing that to the agent was both faster and more reliable than my own scan.

The final fit of the padding, though, I always verified on both a physical device and an emulator. Inset values differ by device — gesture nav versus button nav especially — so code that looks correct can still render misaligned. My grandfathers were temple carpenters, and I am told that even when the drawings matched, they always confirmed the final fit by hand. Going through the screens one by one, I felt this kind of finishing check is the part a human should keep. Drawing a clear line between what to delegate and what to verify yourself is, in fact, the biggest time-saver on cross-app work.

What comes next

I have finished the first pass across the wallpaper apps and am now nailing down the display on three-button and gesture-nav devices on real hardware. Next, I want to extract the inset handling — currently copy-pasted across every app — into a shared module. My near-term goal is to gradually lighten the very structure that forces me to fix every app each time the OS shifts.

If you are facing a targetSdk 36 migration with several apps of your own, I would start by enumerating every opt-out usage across the codebase; it makes the scope of the work far easier to estimate. Thank you for reading.

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-01
Systematize Pre-Release Checks for Sound-Playing Apps with Waveform Rendering and Agents
The 6/26 update let Antigravity render audio files. Drawing on years of shipping sound in healing apps, here is a design that folds loudness, clipping, length, and silence checks into an agent verification loop, with the actual scripts.
App Dev2026-06-21
A Few Low-Density Phones Lost Their Bundled Wallpaper — The drawable vs nodpi Boundary in Play's Density Splits
App Bundle density splits will happily split images that should never be split, dropping a static resource on one density bucket only. Here is how I reproduced it with bundletool and fixed it by moving to drawable-nodpi or disabling density split — with the decision criteria.
App Dev2026-05-16
Surviving New iPhone Resolution Support with Antigravity — 29 Changes in DefineManager.h, One Honest Recap
A real-world account of updating Beautiful HD Wallpapers and three other iOS apps for iPhone Air (420×912) and iPhone 17 Pro (402×874). What Antigravity caught, what it missed, and the two-step prompt pattern that made the 29-edit process manageable.
📚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 →