ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-26Advanced

Green in the Embedded Emulator, Broken on the First Real Device — Putting a Parity Gate on AI-Generated Compose Apps

AI Studio now generates Kotlin/Compose apps from a prompt, runs them in an embedded emulator, and pushes them to a real device over USB — all from one screen. Yet a screen that passed in the emulator can break the first time it lands on a real phone. As a solo developer running several apps, here is how I put a gate that catches device parity issues before they ship.

AI Studio5Antigravity271Jetpack Compose5Android23app-dev42

Premium Article

The first thing that broke was a settings screen in one of my wallpaper apps. I asked AI Studio to rearrange the settings into cards, checked it in the embedded emulator, and the spacing and corners looked exactly right. Reassured, I pushed it over USB to a real device — and only there did I notice the bottom card was half-hidden under the navigation bar.

Green in the emulator, red on the device. That combination is the worst one. Now that generation, execution, and device transfer all connect in a single screen, the round trip of checking has become remarkably fast. But what got faster was checking in the emulator, not checking on a device. For my first few weeks I underestimated that gap, and more than once I took the long way around — shipping to internal test and getting the problem reported back by a tester.

This article is about how to put a verification gate on Compose apps generated by AI Studio or Antigravity that gets ahead of the emulator-versus-device gap. The faster generation gets, the more deliberately slow and firm I keep verification. Here is where I draw that line, and the implementation behind it.

Why the emulator passes and the device breaks

Device differences look capricious, but their sources fall into roughly four buckets. Holding that map in advance tells you where to suspect the generated code.

System insets — notch and navigation bar

The first is system insets. Emulators often run with plain gesture navigation and no cutout, so a generated layout that ignores WindowInsets never shows the problem. Real devices have notches, punch holes, three-button nav, and rounded corners, and that is where elements first disappear. My sunken card was exactly this: the generated code never applied navigationBarsPadding().

Font scale — the weakness of fixed dp

The second is fonts and scale. Real users change display size and font scale. A fixed dp height that looks tidy only at the emulator default (scale 1.0) clips its text on a device at fontScale 1.3. AI-generated layouts tend to reach for fixed heights to make the visuals neat, and that is where they are weak.

GPU rendering — how effects drop

The third is GPU rendering differences. The emulator uses the host GPU, so effects like blur, graphicsLayer, and RenderEffect come out smooth. On a specific device GPU and OS, the same effect can drop or turn into visible jank. For an app like a wallpaper browser that leans on blur, this gap is not negligible.

Locale and RTL — breaking on the flip

The fourth is locale and RTL. If you only ever check in your own language in the emulator, you never notice where elements flip and break under RTL languages like Arabic. Even for a Japanese-market app, store review and overseas testers will hit RTL routinely.

With these four in mind, "check the generated screen on a device" becomes "hunt for device differences along four axes." That finds breakage far faster than staring at the screen vaguely.

Turn the axes into checks that don't disappear

Axes that live in your head slip away on a tired night. I decided to fix the four axes as checks that don't disappear — in code and in previews. The aim is to drop the assumption that a human has to remember them on every generation.

First, insets and font scale get surfaced in Compose previews. Placing extreme-condition previews next to the generated composable puts the breakage in front of you before you ever start the emulator.

// Pin "mean previews" next to the generated code to surface device gaps.
// Raise fontScale, squeeze into a short height, apply RTL.
@Preview(
    name = "Large font + short height",
    fontScale = 1.5f,
    heightDp = 360,
    showBackground = true,
)
@Preview(
    name = "RTL locale",
    locale = "ar",
    showBackground = true,
)
@Composable
private fun SettingsCardStressPreview() {
    AppTheme {
        // Inject a simulated system-bar region to make a missing inset visible.
        Box(Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
            SettingsCardList(items = sampleSettingsItems)
        }
    }
}

Just applying fontScale = 1.5f and heightDp = 360 makes text crammed into a fixed dp height visibly clip in the preview. Adding locale = "ar" reveals where asymmetric padding flips and breaks. Right after generation, you can hand it back to the AI — "this preview is broken, make the layout follow fontScale" — and close the loop before opening the emulator.

Missing insets get sealed permanently on the layout side. For a scrolling list, convert the insets into padding so the end of the content never hides under the navigation bar.

// Flow insets into contentPadding so the list tail never sinks behind the system bar.
LazyColumn(
    contentPadding = WindowInsets.systemBars
        .add(WindowInsets(top = 8.dp, bottom = 8.dp))
        .asPaddingValues(),
) {
    items(settingsItems, key = { it.id }) { item ->
        SettingsCard(item)
    }
}

This makes the sunken-card accident structurally impossible. The point is not to "ask for this fix every time" against the generated code, but to hold it in the template so it is built into the starting point of generation. You confine what you delegate to the AI to a foundation that resists breaking. I strongly recommend hardening this footing before you move into production operation. The pitfall is overtrusting that a human will recall the same caveats on every generation.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A four-axis map — fonts, insets, GPU, locale — for getting ahead of where emulator and device diverge
Concrete steps for turning Compose preview snapshots and the Android CLI into a CI parity gate
An operating contract that separates failures an AI may fix from failures a human must decide
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
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 →

Related Articles

App Dev2026-06-26
Hand Over Generation and Shipping, but Never the Signing Key — Designing Key Custody and Handover for an AI-Driven Pipeline
Even in an era where AI Studio and Antigravity take over everything from generation to internal-test shipping, the app signing key is in a class of its own. Lose it, and that app can never be updated again. As a solo developer who has run several apps for years, here is how I design key custody — separating the upload key from the app signing key, storing it, and planning the handover for the worst case.
App Dev2026-06-24
The Day Generation, Device, and Internal-Test Shipping Became One Step — What I Refused to Hand Over
AI Studio now turns a text prompt into a Kotlin/Compose app and carries it through the emulator, a real device, and Google Play's internal test track from one screen. Behind that convenience sits a question: how much of the moment of shipping do you hand to the machine, and what do you keep in your own hands? Here is where I draw the line as a solo developer running several apps, and the implementation that holds that boundary.
App Dev2026-05-03
Getting Started with AR on Android Using io.github.sceneview — Working Past AnchorNode Errors
A practical guide to building Android AR features with SceneView (io.github.sceneview): AnchorNode error fixes, ARCore session and install handling, plane visualization, and combining Antigravity AI for dynamic scene generation.
📚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 →