RORK LABJP
CLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/monthCLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/month
Articles/Dev Tools
Dev Tools/2026-06-15Advanced

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.

Rork525Expo156AdMob70RevenueCat29State Management7Zustand4Monetization37

Premium Article

I once got an App Store review from a user saying ads still showed even though they had paid. When I dug in, the purchase state itself was read correctly, and ads were properly hidden on the home screen. The only place ads remained was a newer "favorites" screen I had added later.

The cause was easy to find. When I built that screen, I decided whether to show ads with a single line: if (!isSubscribed). But by then my app had not one but three reasons to hide ads — a subscription, a lifetime ad-removal purchase, and a timed unlock where watching a rewarded ad hides ads for a while. The new screen only looked at the subscription, so ads appeared only for users who had bought the lifetime option.

When there is just one condition, if (!isSubscribed) is fine. The trouble starts when monetization quietly adds more reasons to hide ads, and the checks you wrote earlier get left behind. This article is about cutting that drift off at the root: folding the ad-free decision into one piece of state and routing every screen through the same entry point, assuming an Expo app generated by Rork.

Reasons to Hide Ads Pile Up Without You Noticing

When I first shipped, my app had exactly one way to remove ads: a lifetime purchase. Six months later I added a subscription, and then, as a retention play, a timed unlock through rewarded ads. Each arrived at a different time, from a different monetization decision.

This is where the decision logic scatters. You touch the ad code when you add the subscription, and touch it again when you add the reward. Each time, the scope you need to update is supposedly "everywhere ads appear" — but human memory is unreliable. Once you pass ten screens, something always gets missed.

And the miss cuts both ways. Forget to hide ads, and you show ads to people who paid, which leads directly to refunds and lower ratings. Hide them too aggressively, and free users see no ads, so that revenue disappears too. Both drift silently, so you usually notice only after the numbers move.

So the real problem is not "the check is wrong." It is "the same check lives in several places and only one of them gets updated." What needs fixing is not the individual if, but the structure that should gather the decision into a single location.

Folding Three Reasons Into One State

First, make the reasons to hide ads explicit as a type. Code that Rork emits tends to keep logic closed within each screen, so pulling this out into a shared module is the first step.

// lib/adFree/types.ts
export type AdFreeReason =
  | 'subscription'   // an active subscription
  | 'lifetime'       // a one-time ad-removal purchase
  | 'reward'         // a timed unlock from a rewarded ad (has an expiry)
  | null;            // show ads (no unlock)
 
export interface AdFreeState {
  reason: AdFreeReason;
  // only meaningful when reason === 'reward'. Epoch milliseconds.
  rewardExpiresAt: number | null;
}

The key is to not carry an isAdFree: boolean from the start. If all you keep is a boolean, you later lose track of why ads are hidden, and you cannot trace bugs like "the reward expired but ads stayed off." Keeping the reason lets you feed it straight into logs, and lets the UI show things like "ads are off for another 32 minutes."

The three reasons have a precedence. I evaluate them in this order:

  1. lifetime — once unlocked, it is permanent. Highest priority; ads never show regardless of any other state.
  2. subscription — no ads while the entitlement is active.
  3. reward — no ads only while the window has not expired.

This ordering matters when, say, a user who already owns the lifetime purchase happens to watch a rewarded ad. Because lifetime is highest priority, the behavior never gets dragged around by the reward's expiry bookkeeping. Part of the value of gathering the decision in one place is that you write this precedence correctly exactly once.

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
The full implementation of a useAdFree selector that composes subscription, lifetime, and a timed reward unlock with explicit precedence
A timer design that fires exactly at the next expiry instead of every second, so a reward window flips off the moment it should
A measured fix for the bug where rewinding the device clock keeps ads hidden forever, closed with a monotonic-time check
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-16
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.
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-07-14
Designing Seams That Survive AI Regeneration in Rork
Every follow-up prompt to Rork can quietly wipe out logic you wrote by hand. Protecting it with prompts is a patch, not a fix. Here is how to separate generated code from code you own, and draw a boundary that regeneration cannot reach, with working Zustand and service-layer examples.
📚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 →