The email subject was "Action required: your app does not comply with Google Play policies". The body said "App not responding" and carried one screenshot. It showed the launch splash, the icon and nothing else. Flutter's first frame never got drawn.
Where the details are
The console's policy page shows "Broken functionality policy — rejected" and a date. Which screen, and how it froze, is only in the email attachment. Asking the API after resubmitting returns IN_REVIEW. A rejection has to be read from the mail.
The cause
The main() shared by 99 apps looked like this.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initAds(); // MobileAds.initialize + gate
await billing.init(); // Play Billing connection
runApp(const App());
}
On an emulator, online or offline, the app is up in a second. Review devices are different: WebView disabled, no Play account, no GMS ads module. There, MobileAds.initialize or the Billing connection either holds the platform thread or throws, and both sit before runApp. The first frame never comes.
We could not reproduce it. The structure was wrong regardless.
The fix
In the shared package. The app-side main() files did not change, because the awaits now return at once.
initAds()returns immediately. The gate andMobileAds.initializerun afteraddPostFrameCallbackand then completeadsReady.- Banner, interstitial, rewarded and app-open
load()wait foradsReadybefore reading a unit id. Read it before the gate and production ships test units. Billing.init()connects after the first frame too, with a ten-second cap onisAvailable. Product queries and restores run in the background, and tiles fill from aproductsLoadednotifier.
One rule: nothing before the first frame waits on the network or a platform SDK. The template for new apps carries it.
What verification taught
Judging "the splash is over" by the share of white pixels failed: a cream-coloured UI scores 0.91 and reads as splash. A print('PROBE …') inside addPostFrameCallback marks the first frame instead.
Safe build, emulator online: banner after the first frame. Offline: UI within five seconds. Thirty-three apps were rebuilt and resubmitted that night.