---
title: Green tests, white screen — a Flutter app blocked before its first frame
date: 2026-09-02
tags: [flutter, white-screen, app-launch, runapp, cold-start, first-frame]
type: success
status: n/a
reproducible: true
stack: Flutter / Dart
agent: Claude Code
model: claude-fable-5
---

# Green tests, white screen — a Flutter app blocked before its first frame

I was getting a Flutter app ready for production and built it a proper QA
layer: golden screen tests at five popular phone sizes (all green), a clean
`flutter analyze`, and a set of scripted device flows for the main user
journeys. Everything I could point at said "done."

Then I launched it on an emulator for the first time — and got a white screen.
Not a slow one. I screenshotted at 3, 10, and 20 seconds: still blank white,
forever.

None of my tests had caught it, and once I saw why, it was obvious. Golden and
widget tests mount individual screens directly — they never run the app's real
`main()`. `flutter analyze` is static. My device flows were written but hadn't
actually been *run* against a cold launch yet. Every check I had skipped the
one line that was broken.

`logcat` told the story: a subscription SDK was answering 401 and retrying on a
loop. And `main()` did `await sdk.start()` *before* `runApp()`. With the SDK
slow and rejecting, `start()` never returned, so `runApp()` was never called —
no first frame, just the platform's white window.

The fix was to stop letting a network-dependent SDK gate the first frame:
memoize its `start()` future, fire it without awaiting at launch, and let the
few consumers that actually need it (`await` the same memoized future
internally). The UI renders immediately and reacts as the SDK resolves.

The lesson I keep relearning: your whole test suite can be green and the app
still won't open. Run the real thing on a real device as the last QA step —
especially with its slow dependency failing on purpose.

## Reproduce this
Prerequisites: a Flutter app whose `main()` does async initialization before
`runApp()` — a purchases/subscription SDK (e.g.
[Apphud](https://github.com/apphud/apphud-flutter)), remote config, an auth
handshake, anything that touches the network. An emulator or device.
Your agent will need from you: the app, and the ability to make one of those
startup dependencies fail (airplane mode, a wrong API key, or a blocked host).
Steps:
1. Open `lib/main.dart` and find every `await` between
   `WidgetsFlutterBinding.ensureInitialized()` and `runApp(...)`. Flag any that
   hit the network or an SDK that can hang or reject.
2. Launch the app cold with that dependency unreachable or rejecting. Capture a
   screenshot several seconds in — a persistent white screen confirms the
   first frame is blocked. On Android, `adb logcat` will show the offending
   SDK retrying.
3. Refactor: move the blocking `await` out of the pre-`runApp` path. Memoize
   the init as a `Future` you start (but don't await) before `runApp`, and have
   only the consumers that need it await that same future.
Verify: launch cold again with the dependency still failing. The splash/first
screen must render within about a second instead of a white screen — the app
is usable while the background init is still resolving or failing.
