◉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)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●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)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●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/App Dev
◇ App Dev/2026-05-24Advanced

Adding AppLovin MAX to a Rork-Generated iOS Project — A Field Note on Init Order, ATT Consent, and eCPM A/B Testing

A field note from an indie developer on layering AppLovin MAX on top of a Rork-generated iOS app: SDK initialization order, ATT and MAX consent flag sync, and a minimal A/B setup to compare eCPM against AdMob-only.

Rork585AppLovin MAXAdMob73iOS116ATT9eCPM5Mediation7

✦ Premium Article

I am Hirokawa, an indie developer who has been shipping apps as a personal developer since 2014. I recently spent a couple of weeks layering AppLovin MAX on top of an iOS app whose scaffold came out of Rork, and the work turned out to be more delicate than I expected. After years of running AdMob solo across the wallpaper apps that together have crossed 50 million downloads, I rediscovered how easily a single misplaced initialization call can either silence the first banner at launch or quietly miswire the consent state behind the ATT dialog.

Rork is excellent at giving you a working skeleton fast, but the monetization layer is still on you to assemble. This note is the implementation memo I wish I had on hand when I started: it covers what worked when slotting AppLovin MAX into a Rork-generated iOS target, with the goal of running it alongside AdMob through mediation rather than swapping AdMob out entirely.

Why I moved from AdMob-only to running MAX in parallel

The trigger was a stretch of days during the most recent Beautiful HD Wallpapers update where AdMob banner and app-open eCPM hit a clear regional ceiling. Between mid-April and early May, U.S. and Indonesia eCPM each dropped roughly 18% and 22% week over week, while fill rate stayed above 95%. Revenue was sliding without an impression problem, which is exactly when it becomes worth widening the mediation denominator.

Wiring AppLovin MAX in via AdMob mediation lets me keep the GoogleMobileAds call sites essentially untouched. The Rork output here is React Native based, so I added a thin native bridge on the iOS side and contained the entire MAX integration in native code. The JavaScript layer still talks to AdMob; only the iOS target gained an additional SDK and consent path.

In practice, when running two ad SDKs in parallel, the time sink is not adding lines of code. It is keeping the initialization order and consent flags in sync. This note focuses on that.

SDK initialization order with SwiftPM

To add AppLovin MAX to the iOS target via SwiftPM, declare the package dependency on https://github.com/AppLovin/AppLovin-MAX-SDK-iOS in Xcode's Package Dependencies. If you are still migrating Firebase from CocoaPods to SPM at the same time, finish the Firebase SPM migration first to avoid Package.resolved fighting with Podfile.lock. I keep the Firebase migration steps in an internal doc and only add the AppLovin MAX dependency after the SPM-only state is stable.

The launch sequence I now use is:

  1. Inside application(_:didFinishLaunchingWithOptions:), call FirebaseApp.configure() first.
  2. Defer the ATT prompt to applicationDidBecomeActive(_:) rather than asking at launch.
  3. After ATT resolves, call ALSdk.shared().initialize(with: config).
  4. From the AppLovin init completion callback, call GADMobileAds.sharedInstance().start(completionHandler:).
  5. Only after step 4 completes, post an "ads ready" event to the React Native side.

The point is "never call GADMobileAds.start before ATT resolves." If you do, AdMob direct campaigns may lock into a non-IDFA-aware targeting state that does not fully recover until the next process launch.

import AppTrackingTransparency
import AppLovinSDK
import GoogleMobileAds
import FirebaseCore
 
final class AdsBootstrap {
    static let shared = AdsBootstrap()
    private(set) var isReady = false
    private var initInFlight = false
 
    func configureOnLaunch() {
        FirebaseApp.configure()
    }
 
    func startAfterActive() {
        guard !isReady, !initInFlight else { return }
        initInFlight = true
        ATTrackingManager.requestTrackingAuthorization { [weak self] status in
            DispatchQueue.main.async {
                self?.bootstrapMax(attStatus: status)
            }
        }
    }
 
    private func bootstrapMax(attStatus: ATTrackingManager.AuthorizationStatus) {
        let config = ALSdkInitializationConfiguration(sdkKey: AppEnv.applovinSdkKey) { builder in
            builder.mediationProvider = ALMediationProviderMAX
            builder.testDeviceAdvertisingIdentifiers = AppEnv.testDeviceIDFAs
        }
        ALSdk.shared().initialize(with: config) { [weak self] _ in
            self?.bootstrapAdMob(attStatus: attStatus)
        }
    }
 
    private func bootstrapAdMob(attStatus: ATTrackingManager.AuthorizationStatus) {
        GADMobileAds.sharedInstance().start { [weak self] _ in
            self?.isReady = true
            self?.initInFlight = false
            NotificationCenter.default.post(name: .adsBootstrapReady, object: nil, userInfo: [
                "att": attStatus.rawValue,
            ])
        }
    }
}
 
extension Notification.Name {
    static let adsBootstrapReady = Notification.Name("AdsBootstrapReady")
}

The React Native side subscribes to this AdsBootstrapReady notification through a native module and exposes a useAdsReady() hook to JavaScript. Once your call sites gate on a single boolean, the launch-time race conditions stop reproducing in testing.

✦

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 practical sequence for wiring AppLovin MAX into a Rork-scaffolded iOS app via SwiftPM, without touching the React Native ad call sites
✦How to align the ATT prompt, MAX privacy flags, and AdMob initialization so the order does not silently degrade eCPM
✦A minimal Remote Config + Crashlytics A/B setup that lets a solo developer compare AdMob-only vs AdMob + AppLovin MAX in two weeks
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

⟐ Business2026-05-21
Putting AdMob Bidding into Production for a Rork App — Five Networks Bidding in Parallel, eCPM Trends, and Daily Operations
I moved the AdMob mediation layer of my Rork-generated apps from waterfall to bidding, with five ad networks bidding in parallel. Here are my real-world numbers after three weeks of production, the SDK pitfalls, and how I delegate daily monitoring to Claude in Chrome.
◇ App Dev2026-04-26
Native iOS Apps with Rork: SwiftUI to TestFlight
New iPhone projects in Rork are generated in Swift and SwiftUI, and Rork Pro carries them all the way to the App Store. How to tell an old Expo project from a new one, costs in credits, the skeleton prompt, device testing, when to drop into Xcode, and TestFlight submission.
⟐ Business2026-09-06
Your Blended eCPM Dropped and Revenue Went Up: Splitting the Change Into Country Mix and Rate
Blended eCPM is a weighted average, not a price. Here is how I split a month-over-month change into a country-mix effect and a rate effect that reconcile exactly to the total, plus what to check before you cut a low-eCPM country.