●RORKMAX — Rork Max generates pure Swift instead of React Native, enabling true native apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●APPLE — Rork's 2026 direction has a clear theme of native empowerment across the Apple ecosystem●EXPO — Standard builds run on React Native and Expo, so you're left with a real project structure and code you can keep working on●FUNDING — Rork recently raised $15M and now sees over 743,000 monthly visits with 85% growth●PRICING — Rork is free to start, with paid plans from $25/month and Rork Max at $200/month●CROSS — Rork builds iOS, Android, and web from a single prompt, finished off with a bit of follow-up tweaking●RORKMAX — Rork Max generates pure Swift instead of React Native, enabling true native apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●APPLE — Rork's 2026 direction has a clear theme of native empowerment across the Apple ecosystem●EXPO — Standard builds run on React Native and Expo, so you're left with a real project structure and code you can keep working on●FUNDING — Rork recently raised $15M and now sees over 743,000 monthly visits with 85% growth●PRICING — Rork is free to start, with paid plans from $25/month and Rork Max at $200/month●CROSS — Rork builds iOS, Android, and web from a single prompt, finished off with a bit of follow-up tweaking
Lessons from running AdMob apps as an indie developer: how UI design alone can lift ad revenue. Covers high-revenue layouts in Rork, frequency-cap implementation, how consent (ATT/UMP) shapes eCPM, and a Firebase testing loop, with working code.
When I ran AdMob apps as an indie developer, checking the revenue report with my morning coffee was a daily ritual.
One day I nudged the interstitial on the game-over screen a few dozen pixels, and the following week ARPU jumped almost 20%. Not a single line of logic changed. The only thing that moved was where the ad met the player's eyes.
That was when it clicked: AdMob revenue isn't a number the ad network hands you. It's a number your UI decides. Google sets the CPM, but you decide when and where a user meets an ad.
What follows is a mix of the wins and the mistakes I accumulated running AdMob apps, mapped onto a concrete Rork workflow: choosing ad formats, capping frequency, handling consent, and verifying everything in Firebase, with code along the way.
Break revenue apart and you find the levers you actually control
AdMob revenue is a product of three terms:
Revenue = Impressions × CTR × CPM
Most developers fixate on CPM, but advertiser bidding sets that. The two terms you design are Impressions (how many ads appear per session) and CTR (how naturally those ads get tapped). Both are UI questions, and both turn directly into money.
What actually changed, before and after
For one casual game I only touched the UI:
Before — DAU 5,000, session 3 min, 1.2 impressions/session, CTR 3%, CPM 50 yen, daily revenue ~900 yen.
After — DAU 5,000 (unchanged), session 3.5 min (ads stopped getting in the way), 2.0 impressions/session, CTR 5%, CPM 50 yen (unchanged), daily revenue ~2,500 yen.
Same DAU, same CPM, 2.8x revenue. The placement design did all the work.
Ad placement has a comfortable order
Whether an ad lands depends on the user's state of mind. In my apps, this order held up consistently.
Best: the game-over screen. The moment players see "Game Over," their guard is down and they're waiting for what's next. A fullscreen interstitial here pushed CTR to 8–12%, versus 1–2% for a banner, and it rarely felt intrusive.
Next: pause and menu screens. Users paused on their own, so they're primed to wait. A 300x250 banner above the "Resume" button earns impressions without friction.
Avoid: mid-gameplay. Interrupting focus wrecks the experience. I tried it once early on; "too many ads" filled the reviews and my rating dropped. Short-term impressions rose, but churn and uninstalls cost more than I gained.
✦
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 Swift implementation of interstitial frequency caps (cooldown + games-between-ads), plus the lesson learned when I shipped without one
✦How ATT/UMP consent flows move your eCPM, and where to place the priming screen to lift opt-in rates
✦How to stop confusing monthly ARPU with daily ARPDAU, worked through a real DAU 5,000 / 150,000 yen app
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.
Banners build the impression floor, interstitials capture high CTR at natural breaks, and rewarded ads keep satisfaction intact because the user opts in. Layering all three balances revenue and experience.
Skip the frequency cap and revenue goes down
This is where I paid the highest tuition.
Interstitials are strong, so it's tempting to fire one at every break. But every break exhausts users fast, sessions shrink, and shorter sessions mean fewer impressions. Greedy placement quietly eats your revenue.
The fix is a minimum cooldown between fullscreen ads: remember when you last showed one, and refuse to show another until enough time and enough games have passed.
import Foundationimport GoogleMobileAdsfinal class InterstitialManager { static let shared = InterstitialManager() private var interstitial: GADInterstitialAd? private var lastShownAt: Date? private let cooldown: TimeInterval = 90 // at least 90s since last show private var gamesSinceLastAd = 0 private let minGamesBetweenAds = 2 // at most 1 ad per 2 games func canPresent() -> Bool { gamesSinceLastAd += 1 guard gamesSinceLastAd >= minGamesBetweenAds else { return false } if let last = lastShownAt, Date().timeIntervalSince(last) < cooldown { return false } return interstitial != nil } func present(from root: UIViewController) { guard canPresent(), let ad = interstitial else { return } ad.present(fromRootViewController: root) lastShownAt = Date() gamesSinceLastAd = 0 loadNext() // preload the next one right away }}
Capping on both time and game count is the trick. After adding this two-axis cap, my sessions recovered and monthly ARPU actually rose. Showing fewer ads and earning more sounds backwards, but it's easy to miss if you underrate frequency capping.
Consent (ATT/UMP) is UI, and it sits at the root of revenue
Rarely discussed in technical posts, yet just as decisive as placement: the consent UI.
On iOS you have App Tracking Transparency (ATT) plus GDPR/privacy consent through AdMob's User Messaging Platform (UMP). Every "don't allow" cuts off personalized ads and drags eCPM down. How you present those dialogs feeds straight into your unit price.
What helped me was not firing the system prompt cold, but inserting a priming screen first — explaining, in the app's own voice, why relevant ads keep the app running. Asking for the system permission only after that noticeably improved opt-in rates.
import AppTrackingTransparencyimport UserMessagingPlatformfunc requestConsentThenTracking() { // 1. Update UMP (GDPR, etc.) consent info first let params = UMPRequestParameters() UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: params) { error in guard error == nil else { return } UMPConsentForm.loadAndPresentIfRequired(from: nil) { _ in // 2. Then request ATT (iOS 14.5+) if #available(iOS 14.5, *) { ATTrackingManager.requestTrackingAuthorization { _ in // 3. Only now initialize AdMob GADMobileAds.sharedInstance().start(completionHandler: nil) } } else { GADMobileAds.sharedInstance().start(completionHandler: nil) } } }}
Order matters: UMP consent, then ATT, then AdMob init. Break it and the SDK runs before consent, risking policy violations and broken delivery. Reframe consent as a revenue task that protects eCPM, and its UI priority rises.
Don't forget app-ads.txt either. Drop the file at the root of your app's official site with your AdMob publisher ID, and your inventory gets distinguished from spoofed stock, stabilizing bids. Small effort, real effect.
Brief Rork with ads in mind from the start
Here's where AI assistance earns its keep. When you generate an app in Rork, design for ads from the first prompt instead of bolting them on. Spell out placement explicitly.
Create an iOS game "Block Rush" with:
Game Mechanics:
- Stack falling blocks, game over when they reach the top
- Score: 10 per block, 3 lives per game
Ad Integration (PRIMARY GOAL):
- Bottom banner (320x50) always visible during gameplay,
below the score, never overlapping the game area
- Fullscreen interstitial on game over, then "Retry" and "Menu"
- "Watch Ad + 1 Extra Life" rewarded button, shown only when lives < 3
Design:
- Dark theme, score top-left, lives top-right, no overlap with ad areas
- Large buttons (44pt min), portrait and landscape
Analytics Ready:
- Firebase hooks for game_start, game_over, ad_impression, ad_click
Rork lays out ad and game areas so they don't collide. After generating, run it in the simulator and check: banner fixed and clear of the game area, interstitial on game over, return-to-menu after close, placement holding across orientations. Fix anything in plain language:
The bottom banner overlaps the game controls.
Move the game area up by 60 pixels so the banner has its own space.
It's faster than rewriting layout by hand, and it frees you to focus on testing placements. I usually generate two or three layout variants and feed them into the A/B test below.
Add your tags, and don't confuse ARPU with ARPDAU
Generated code often lacks analytics events. Add Firebase events at the ad moments by hand.
The place people stumble is the ARPU math — I did too. Say AdMob shows 150,000 yen over 30 days with DAU 5,000. It's tempting to write "ARPU = 150,000 ÷ 5,000 = 30 yen," but that's monthly revenue per user, not a daily figure.
The daily metric is ARPDAU (Average Revenue Per Daily Active User): that day's revenue ÷ that day's DAU. For 150,000 yen over 30 days, that's ~5,000 yen/day, so ARPDAU is about 1 yen.
Metric
Formula
This example
Monthly ARPU
Monthly revenue ÷ DAU
150,000 ÷ 5,000 = 30 yen
ARPDAU
Daily revenue ÷ DAU
5,000 ÷ 5,000 = 1 yen
Judge A/B tests on ARPDAU, which catches daily movement quickly. Use monthly ARPU for overall scale. Keep them separate and your test reads stay honest.
Verify hypotheses with Rork × Firebase
Never stop at "it should be better." Confirm with an A/B test. Rolling a guess out to everyone and losing revenue is the most wasteful mistake there is.
Firebase A/B Testing plus Remote Config serves different layouts to different users.
In the app, fetch Remote Config at launch and switch on the value.
let remoteConfig = RemoteConfig.remoteConfig()remoteConfig.fetch { _, _ in remoteConfig.activate() let position = remoteConfig.configValue(forKey: "banner_position").stringValue position == "top" ? placeBannerAtTop() : placeBannerAtBottom()}
Track four metrics only:
Metric
Meaning
Pass line
ARPDAU
Revenue per user per day
B over A by +15%
CTR
Ad click rate
B over A by +20%
Session length
Time per session
B ≥ A (no drop)
Churn
Uninstall rate
B ≤ A (no rise)
After ~14 days, if ARPDAU is A=1.5 / B=1.8 yen (+20%), CTR A=4.2% / B=5.1% (B wins), sessions roughly even, churn even or lower, roll out B. The key is always pairing revenue with churn. Watch one metric alone and short-term numbers will fool you.
Compound small wins on a monthly cycle
Indie time is scarce, so I favor one hypothesis per month. Week 1: study Firebase and AdMob data for an hour and write the hypothesis. Week 2: generate the new layout in Rork, define Remote Config variants, verify in TestFlight. Week 3: ship to the test group and monitor. Week 4: analyze and roll out the winner.
Over three months this stacks up: +20% from banner position, +15% from interstitial frequency, +12% from rewarded placement. Compounded, ARPDAU ends up around 1.5x.
Big jumps are rare. But one data-backed improvement a month turns into a real gap over half a year. Personally, this quiet accumulation has outperformed every flashy single move I've tried.
If you're wrestling with AdMob revenue too, I hope this gives you one idea to try tomorrow.
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.