RORK LABJP
MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode requiredSTACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decisionFOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generationBUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebaseFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)PRICING — It is free to start, with paid plans from $25/month, so you can try before committingMAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode requiredSTACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decisionFOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generationBUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebaseFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)PRICING — It is free to start, with paid plans from $25/month, so you can try before committing
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.

Rork415Expo84AdMob63ATT8startup-sequenceRevenueCat24state-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-15
The Day a Third Reason to Hide Ads Appeared — Folding Rork App Ad-Free Logic Into One Place
Ads show only on one screen for paying users, or ads never show for free users. The usual cause is that the condition for hiding ads is scattered across the code. Here is how I fold three reasons — subscription, lifetime purchase, and a timed reward unlock — into a single state and route every ad through one hook, written as an implementation note from running six apps as an indie developer.
Dev Tools2026-06-14
Building Rork Subscriptions Around RevenueCat Entitlements — Access Checks, Offering-Driven Paywalls, and Restore
Implementation notes for adding subscriptions to a Rork (Expo) app with RevenueCat. Make Entitlements the single source of truth for access, drive the paywall from Offerings so you can change prices remotely, wire up restore and the customer-info listener, and avoid the sandbox traps — all with working code.
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.
📚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 →