RORK LABJP
RORK MAX — Rork Max can now build native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProPUBLISH — Rork Max offers two-click App Store publishing with no Xcode required, cutting the friction of getting an app shippedEXPO — The standard Rork is built on React Native (Expo), generating native iOS and Android apps from plain-English descriptionsPRICING — Rork is free to start, with paid plans beginning at $25/month, an accessible tier for solo developersFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz) as investment keeps flowing into AI app buildersREVIEW — In real use the keys are generated-code readability and maintainability, Expo-related constraints, and how easily billing, push, and ad SDKs slot inRORK MAX — Rork Max can now build native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProPUBLISH — Rork Max offers two-click App Store publishing with no Xcode required, cutting the friction of getting an app shippedEXPO — The standard Rork is built on React Native (Expo), generating native iOS and Android apps from plain-English descriptionsPRICING — Rork is free to start, with paid plans beginning at $25/month, an accessible tier for solo developersFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz) as investment keeps flowing into AI app buildersREVIEW — In real use the keys are generated-code readability and maintainability, Expo-related constraints, and how easily billing, push, and ad SDKs slot in
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.

Rork402Expo73AdMob62RevenueCat22State Management5Zustand3Monetization31

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-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.
Dev Tools2026-05-30
Managing Native Settings Across Rork-Exported Apps with a Custom Expo Config Plugin
Tired of re-typing your AdMob ID and ATT string every time prebuild wipes your Info.plist? Here is how I made native settings reproducible with a custom Expo config plugin and shared it across six wallpaper 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 →