Phasing AppLovin MAX into iOS with Antigravity Editor: A Four-Week Implementation Record
A four-week record of phasing AppLovin MAX into four wallpaper apps that had been running on AdMob alone. How I split the work between Antigravity Editor's Inline Edit and Agent Mode, settled the SDK initialization order, and designed the revenue verification gates—written with real code and the decisions behind each step.
Run AdMob alone, or add AppLovin MAX into a mediation stack? Anyone running iOS apps as an indie developer eventually faces this decision. In my case, I had been operating wallpaper apps that have accumulated roughly fifty million downloads in total, and I decided to phase AppLovin MAX into four of them first. Antigravity Editor was my primary editor throughout, and the four weeks doubled as a chance to learn where Inline Edit fits versus where Agent Mode earns its place.
This is not a tutorial on how to integrate AppLovin MAX. It is closer to a decision log: what order I verified things in to avoid losing revenue, and which Antigravity features I leaned on at each step. The SDK wiring itself should follow the official documentation. What I want to share is the judgment that comes around it when an indie developer phases a change into live apps.
Why I added AppLovin MAX, and where I decided not to
I have been releasing personal iOS apps since 2014, and for much of that time AdMob alone covered my needs. The familiarity of Google account management was a real benefit, and the numbers were steady enough that adding a mediation layer felt like extra surface area without enough upside.
What changed my mind was an inventory I had Antigravity Agent run across three years of revenue data. It surfaced that eCPM in parts of North America and Europe had drifted down enough that AppLovin's server-side bidding looked structurally promising. Japan and most of Asia, on the other hand, were still healthy on AdMob, with no clear reason to disturb a working setup.
That observation pushed me away from a "switch everything at once" plan and toward "phase AppLovin MAX into four wallpaper apps first, and only expand to the ones whose four-week numbers come back positive." Both of my grandfathers were temple carpenters, and I grew up watching them extend rather than replace. That instinct shows up in how I approach live revenue too: add alongside, do not rip out.
A four-week schedule with verification gates
To keep the decisions reusable, I wrote down the goal and the gate for each week before touching code. I parked the schedule in the Antigravity Editor project notes so agents could reference it as well, and that turned out to matter more than I expected.
Week
Goal
Gate condition
Week 1
Add the SDK via SPM, keep AdMob serving as before
Crash-free sessions ≥99.85%, AdMob impressions within ±2% of the prior week
Week 2
Confirm test ads load through MAX Mediation Debugger, finalize init order
Test ads visible across all four apps, no contention inside application(_:didFinishLaunchingWithOptions:)
Week 3
Start production traffic at 10% with Remote Config gating
Minimum sample to compare eCPM cleanly against the AdMob-only cohort (≥50,000 impressions)
Week 4
Expand to 30% / 50% / 100% by region
ARPDAU up ≥3%, or flat with measurable fill-rate improvement
If a week missed its gate, I stayed on that week. I had decided in advance that "we will make it up next week" was not allowed, because the temptation to use that line was guaranteed to appear.
✦
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 four-week schedule for moving from AdMob-only to AppLovin MAX mediation without sacrificing revenue, with weekly verification gates
✦Concrete criteria for choosing between Antigravity Editor's Inline Edit and Agent Mode during SDK integration, refined over three weeks of daily use
✦A shared `MediationCoordinator` design across four wallpaper apps that minimizes per-app differences through a single Remote Config layer
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.
Adding the SDK and settling initialization order (Week 1)
Week 1 started with adding the AppLovin MAX SDK via SPM. Some of my apps had finished migrating off CocoaPods, others were still mixed. I asked Antigravity Agent to estimate the operational cost of "SPM-only" versus "mixed" over the next twelve weeks, and the comparison made it easy to commit to SPM-only across the four apps.
For initialization order, AppLovin recommends starting as early as possible. The catch is that AdMob is already initialized inside application(_:didFinishLaunchingWithOptions:), so the design needs to avoid contention. I wrote a small shared coordinator, mostly with Antigravity Editor's Inline Edit, that ended up looking like this:
import GoogleMobileAdsimport AppLovinSDK@MainActorfinal class MediationCoordinator { static let shared = MediationCoordinator() private(set) var isReady: Bool = false private let queue = DispatchQueue(label: "ad.mediation.init") private var didStart = false func bootstrap(completion: @escaping (Bool) -> Void) { queue.async { [weak self] in guard let self, self.didStart == false else { return } self.didStart = true let group = DispatchGroup() group.enter() GADMobileAds.sharedInstance().start { _ in group.leave() } group.enter() ALSdk.shared()?.mediationProvider = "max" ALSdk.shared()?.initializeSdk { _ in group.leave() } group.notify(queue: .main) { self.isReady = true completion(true) } } }}
My first version did not use DispatchGroup. It managed "both done" with an if/else state machine that was deceptively brittle—one of the two finishing first led to a branch I had not properly handled, which nearly shipped a crash to TestFlight. I asked Agent Mode for "the canonical pattern for parallel SDK init that won't deadlock or race," and the rewrite to DispatchGroup happened the second half of Week 1.
Inline Edit was the right tool for "rename this," "rewrite this method," "tidy this signature"—the small surgical edits that come up dozens of times a day. Agent Mode earned its keep on "show me three options for the parallel init pattern" and similar design-shaped questions. Week 1 doubled as a deliberate practice week for the split.
Getting test ads to actually appear (Week 2)
Week 2 should have been straightforward: pop into MAX Mediation Debugger and confirm test ads. Two of the four apps refused to render them even though the ad unit IDs and SDK keys were correct.
The cause was banal but easy to miss: Info.plist was carrying the SKAdNetworkItems list from the AdMob-only era, missing about fifteen entries AppLovin requires. I had Agent Mode diff the current Info.plist against the latest official SKAdNetwork list from AppLovin, then produced an XML patch I merged into each project.
<key>SKAdNetworkItems</key><array> <dict> <key>SKAdNetworkIdentifier</key> <string>cstr6suwn9.skadnetwork</string> </dict> <!-- Existing AdMob-era entries --> <dict> <key>SKAdNetworkIdentifier</key> <string>ludvb6z3bs.skadnetwork</string> </dict> <!-- Additional entries required by AppLovin (use the live official list) --></array>
The official list changes, so I deliberately did not hardcode it anywhere. The rule I settled on is "before each release, have Agent Mode verify the current list and reconcile against Info.plist." Folding that into the release checklist removed a category of bugs that I might otherwise have shipped without noticing.
The week's other time sink was ATT. With AdMob only, I had been showing the dialog right after launch. AppLovin's test ad loading required the consent state to be resolved before the SDK was fully usable, so the first few sessions failed to render ads. I asked Agent Mode for a wrapper that would guarantee ATT resolution before continuing SDK init, and rewrote the bootstrap flow into an await-style sequence based on the result.
A 10% rollout backed by Remote Config (Week 3)
Week 3 was the first time real users met the new path. Three principles mattered: never flip straight to 100%, always keep a Remote Config kill switch, and do not draw conclusions before the sample is large enough. I wired Firebase Remote Config into the decision like this.
struct MediationConfig { let enableAppLovin: Bool let appLovinPercentage: Int static func resolve() -> MediationConfig { let remote = RemoteConfig.remoteConfig() let enabled = remote.configValue(forKey: "mediation_enable_applovin").boolValue let pct = Int(remote.configValue(forKey: "mediation_applovin_pct").numberValue.intValue) return MediationConfig(enableAppLovin: enabled, appLovinPercentage: pct) }}@MainActorfinal class AdLoader { func load(unitId: String) async throws -> Ad { let config = MediationConfig.resolve() if config.enableAppLovin && shouldUseAppLovin(percentage: config.appLovinPercentage) { return try await MAX.load(unitId: unitId) } return try await AdMob.load(unitId: unitId) } private func shouldUseAppLovin(percentage: Int) -> Bool { let bucket = UserBucket.current % 100 return bucket < percentage }}
UserBucket.current is a device-stable hash mod 100. I have rebuilt this primitive a few times over the years across the fifty-million-download portfolio, because consistent bucketing across A/B tests and phased rollouts has paid off every time.
The sampling gate was "≥50,000 impressions, eCPM confidence interval inside ±5%." Fifty thousand was derived from typical daily impressions in this app class; the threshold needs to scale with the app. I asked Agent Mode to project, from current DAU and average impressions per user, how many days to reach 50,000—about three to five for the wallpaper apps. That told me the 10% rollout was safe to start.
Expanding to 30% / 50% / 100% by region (Week 4)
Week 4 expanded by region rather than uniformly. North America and Western Europe went to 50%. East Asia to 30%. Southeast Asia stayed at 10%. The idea is to confine risk to the geographies where AppLovin's mediation has the most structural reason to outperform.
I considered splitting the Remote Config key per region, but settled on a single key plus region logic on the Swift side. The reason is purely about edit velocity: rewriting a region check inside Antigravity Editor's Inline Edit is faster than maintaining many Remote Config keys.
struct RegionPolicy { static func appLovinShare(for region: String?) -> Int { switch region { case "US", "CA", "GB", "DE", "FR": return 50 case "JP", "KR", "TW", "HK": return 30 case "TH", "VN", "ID", "PH": return 10 default: return 30 } }}
By the end of Week 4, the two apps focused on North America and Europe had moved to ARPDAU +4.2% and +3.8%. One app in East Asia was flat, with fill rate improving slightly. The Southeast Asia app stayed at 10% as planned and showed no measurable change. The takeaway is that mediation is not magic across the board—the numbers came back differently per region, and that visibility was the real win.
How I split Inline Edit and Agent Mode in practice
After four weeks, the split between Inline Edit and Agent Mode became clear enough that I can put it in words. SDK integration—where the official docs are authoritative and the task is to translate them into code—is overwhelmingly Inline Edit territory. The moment a design decision shows up, Agent Mode pays back the latency.
Inline Edit is right when: I am rewriting a few lines inside a function, aligning naming across files, adding a single test case, or tightening an error message. The feedback loop is tight and the result is easy to verify visually.
Agent Mode is right when: I want three options for the parallel init pattern, a diff between the official list and my current state, or a written rationale I can later reread. I rarely accept the first output as the final answer. I use it as material to compare against and to refine my own intent.
After several weeks of using both dozens of times a day, the decision of which to invoke happens automatically. Routing between tools is the kind of skill you can only build through repetition.
What I want to remember when expanding to the next batch of apps
Three lessons I want to carry forward into the next batch:
First, write the gate conditions before deciding the rollout percentages. Concrete thresholds like "≥50,000 impressions" and "ARPDAU +3%" save you from the temptation of judging early. Second, when initialization order causes contention, do not patch with another if statement—pull Agent Mode in, choose a clean pattern, and rewrite. The if-patches accumulate into untouchable zones surprisingly fast. Third, ship the Remote Config kill switch from day one. Knowing you can revert instantly is what makes the percentage rollouts feel principled rather than hopeful.
The "extend, do not replace" instinct I picked up from grandfathers who built temples turned out to be exactly the right disposition for a live revenue surface. Keeping AdMob in place while adding AppLovin MAX as an additional layer is the main reason four apps reached production without a single rollback over four weeks.
The next concrete step is to revisit ARPDAU and fill rate at the twelve-week mark and decide which apps continue on the AppLovin MAX path and which fall back to AdMob-only. A phased migration does not end at adoption; it ends at the re-evaluation twelve weeks later.
Thank you for reading. I hope it helps in your own integration work.
Share
Thank You for Reading
Antigravity 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.