◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 27 days left. If your app calls it, check the migration target●SDK 58 — Moves to React Native 0.88. R8 is now on by default in Android release builds, and iOS prebuild generates a SceneDelegate●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 27 days left. If your app calls it, check the migration target●SDK 58 — Moves to React Native 0.88. R8 is now on by default in Android release builds, and iOS prebuild generates a SceneDelegate●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-06-16Advanced

I Initialized Ads Before Restoring Purchases, and Paying Users Saw a Banner Flash — Cold-Start Ordering for Rork (Expo) Apps

Consent, ATT, ad SDK init, purchase restore, and remote config all try to run in the same few hundred milliseconds at launch. Get the order wrong and a paying user sees a banner flash, or measurement fires before consent in the EEA. Here is how I fold a Rork-generated Expo app's startup into a single orchestrator and kill the races by design.

Rork611Expo234AdMob75ATT9startup-sequenceRevenueCat30state-management

✦ Premium Article

After adding ads and in-app purchases to a Rork-generated Expo app, a TestFlight reviewer told me that "a banner flashes for a split second at launch, then disappears." The account was a paying one. It was hard to reproduce: it only happened on a cold start, meaning the very first open after fully quitting the app.

The cause was not a feature bug. It was the order of the startup work. The ad SDK finished initializing first, the first banner began to draw, and only then did the purchase restore complete and decide "this user should not see ads." So the banner appeared for an instant and was pulled.

In those few hundred milliseconds at launch, consent gathering, the ATT permission dialog, ad SDK initialization, purchase restoration, and remote config fetching all try to run at once. Each can be implemented correctly and still, if the order does not line up, you show ads to people who paid, or run measurement before consent for readers in the EEA. This is an implementation note on folding that startup sequence into a single orchestrator so the ordering accidents are designed out. Running six apps in parallel as a solo developer, rewriting this part into one explicit serial flow was the turning point where launch-time bugs dropped the most.

What Actually Competes in the First Few Hundred Milliseconds

List the initialization you want on a cold start and it usually comes down to five things.

  • Gathering user consent (UMP in the EEA; I leave the detailed GDPR/UMP setup to a separate article)
  • The ATT (App Tracking Transparency) permission dialog
  • Ad SDK (AdMob / mediation) initialization
  • Restoring purchase state (subscription, one-time remove-ads, reward-based timed unlock)
  • Fetching remote config and feature flags

The problem is that if you fire each of these independently inside its own useEffect, the order in which they finish varies from run to run. On a fast network the purchase restore finishes first; on a slow one the ad init finishes first. It is the classic non-reproducible bug, and for a while I, too, filed it under "happens sometimes."

The reason the varying order hurts is that there are dependencies between the stages. Ads must not run before consent. Whether to show ads cannot be decided until purchase state is settled. Ignore these dependencies and run everything in parallel, and the dependent stage acts during the brief moment its dependency has not finished. That banner flash at the top was exactly the instant when "purchase restore did not make it in time for ad init."

Why "Consent → ATT → Ad Init" Cannot Be Broken Apart

The first unbreakable serial chain is the three of consent, ATT, and ad initialization.

For readers in the EEA, you must not run measurement (App Measurement) before consent is obtained. In react-native-google-mobile-ads you set delay_app_measurement_init to true in the config so that measurement initialization is delayed until the first ad request. Without this, measurement starts the moment you are still presenting the consent dialog.

ATT is iOS's tracking permission. If you configure an ATT message in your AdMob console, the UMP consent flow will present the ATT dialog right after, so you do not have to sequence those two yourself. The important part is to call MobileAds().initialize() only after that consent-and-ATT flow completes. Reverse the order and the SDK initializes before you can read the consent result, so personalization eligibility is not reflected correctly.

Gather consent and ATT first, and initialize the ad SDK only once that resolves. The chain is short, but it is the part you must never parallelize. In code:

import mobileAds from "react-native-google-mobile-ads";
import {
  AdsConsent,
  AdsConsentStatus,
} from "react-native-google-mobile-ads";
 
// Resolve consent + ATT in one flow.
// With an ATT message configured in AdMob, UMP also presents the ATT dialog.
async function gatherConsentThenInitAds(): Promise<void> {
  try {
    // 1) Refresh consent info; show the form (and ATT) if required
    const consentInfo = await AdsConsent.requestInfoUpdate();
    if (
      consentInfo.isConsentFormAvailable &&
      consentInfo.status === AdsConsentStatus.REQUIRED
    ) {
      await AdsConsent.showForm();
    }
  } catch (e) {
    // If the consent flow fails, do not block launch.
    // Continue with ads treated as non-personalized.
    console.warn("[bootstrap] consent flow failed, continuing non-personalized", e);
  }
 
  // 2) Only after consent/ATT resolves do we initialize the ad SDK
  await mobileAds().initialize();
}

The wide try/catch here is deliberate. The consent flow can fail depending on the user's network or region, so on failure we do not stop the launch itself; we let ads continue as "non-personalized." In a startup sequence, the trick is to decide per stage how far a failure is allowed to be swallowed, so one stage's failure does not take the whole launch down.

✦

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
✦You will get a complete, copy-and-run bootstrap function that folds consent, ATT, ad init, and purchase restore into one ordered sequence
✦You will learn how to tell a stage that must run serially from one that can run in parallel, so you remove ordering bugs without inflating launch time
✦You will be able to pre-empt the easy-to-miss ordering mistakes (a banner flashing for paying users, measurement firing before consent in the EEA) with exact repro conditions
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

Rork 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

⬡ Dev Tools2026-06-12
Building a Developer Debug Menu Into Your Rork App — Verify Ads, Purchases, and Remote Config Before Release
A production-safe developer debug menu for Rork apps — switch environments, force test ads, simulate entitlements, and override Remote Config, with working TypeScript code and the pitfalls I hit running six apps.
⬡ Dev Tools2026-04-24
Rork × AdMob UMP × ATT Consent Management for Production — Keeping Revenue Alive in Global Releases
A production playbook for wiring UMP and ATT into your Rork app so you stay GDPR-compliant while protecting AdMob revenue, with working code throughout.
⬡ Dev Tools2026-10-30
It runs in development but the iOS Release build dies on launch — checking whether a missing scheme is the cause
In an expo-router app, an app.json with no scheme can crash only the iOS Release build at launch. Why Debug and Android survive, the two ways to fix it, how to think about leaving the scheme out on purpose, and a pre-submission check.