RORK LABJP
ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/monthACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month
Articles/App Dev
App Dev/2026-06-25Advanced

Why Reinstalling Users Don't Return to 'First Run' — A First-Launch and State-Persistence Design for Rork (Expo) Apps

How to fix broken first-launch detection, onboarding, and free-trial state on reinstall in a Rork-generated Expo app, by treating it as an asymmetry in storage persistence. Covers what survives uninstall on iOS vs Android, separating install and version axes, and server-authoritative entitlements, with implementation.

Rork452Expo105first launchreinstallKeychain3AsyncStorage10state persistencetrial

Premium Article

Why Reinstalling Users Don't Return to "First Run"

One day I got a message about one of my indie apps: "I switched phones and onboarding never showed." Normally you worry about the opposite — onboarding showing again on reinstall is the more common complaint. Digging in, the cause was that each storage type differs in whether it's wiped or kept on uninstall. The flag I used for first-launch detection was wiped on one device and survived on another.

What clicked then was that reinstall bugs are less bugs than the result of the developer not grasping the asymmetry of storage persistence. This article tackles that asymmetry head-on and shares the design — with implementation — for keeping first-launch detection, onboarding, and free trials from breaking on reinstall.

What Is "Wiped" and What "Survives" on Uninstall

Get the facts straight first. When an Expo/React Native app is deleted, what's wiped and what survives differs by storage and OS. iOS Keychain in particular is counterintuitive: items can survive even after the app is removed.

StorageAfter uninstall on iOSAfter uninstall on Android
AsyncStorage / MMKV / filesWipedWiped
SecureStore (Keychain)Can surviveWiped
SharedPreferencesCan be restored via auto-backup
iCloud Keychain synced itemsSurvive across devices
Server-side accountSurvives (device-independent)Survives (device-independent)

This table explains nearly all reinstall behavior. Put a first-launch flag in AsyncStorage and it's wiped on reinstall, so onboarding shows again. Put "trial consumed" in SecureStore and on iOS it survives deletion, so the trial doesn't come back after reinstall (whether you want it back is a design decision). On Android SecureStore is wiped too, but SharedPreferences can be restored depending on the auto-backup setting.

In other words, "wiped or kept" is a property determined by your storage choice — not chance. The design starts here: for each piece of state, decide up front whether it should be cleared or survive on reinstall, then place it in the storage that matches.

Decide "How It Should Behave" First

Desired behavior differs by state. Every time I add a piece of state, I ask myself three questions.

  1. Should this state be reset on reinstall (e.g., local drafts, UI settings)?
  2. Should this state survive reinstall (e.g., an identifier to prevent fraudulent trial re-acquisition)?
  3. Should this state be held by the server, not the device (e.g., purchases, subscriptions, accounts)?

The table below is my default assignment.

StateDesired behaviorWhere it lives
Onboarding completedFine to re-show on reinstallAsyncStorage
UI settings / themeFine to clearAsyncStorage
Free trial consumedShould survive (prevent re-acquisition)Server (SecureStore as aux)
Purchase / subscription entitlementSurvives, device-independentServer / store (StoreKit/Play)
Install identifierShould survive on the same deviceSecureStore

The key point is never judge money- or fraud-related state with device storage alone. AsyncStorage is wiped on reinstall, so putting "trial used" there lets a user re-acquire the trial infinitely by deleting and reinstalling. Rely on SecureStore alone, on the other hand, and behavior splits between iOS and Android. Money decisions should ultimately be held by the server, with device storage demoted to a cache for speed.

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
Lays out the asymmetry between storage that is wiped on uninstall and storage that survives — per iOS and Android — and shows structurally why first-launch detection breaks on reinstall
A durable install-identity implementation that never confuses fresh/update/reinstall, plus server-authoritative trial and entitlement checks — the exact shape I run on my own indie apps
Treats first launch (install axis) and post-update What's New (version axis) as separate axes, with Android auto-backup pitfalls and a migration checklist in one place
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

App Dev2026-06-25
Why Untranslated Strings Leak to Production Every Time You Add a Language — A Catalog and Gap-Detection Design for Rork (Expo) Apps
A design for stopping untranslated strings from leaking into production after you localize a Rork-generated Expo app. Covers a single source-of-truth catalog, CI-based missing/extra key detection, explicit fallback chains, plurals, and pseudolocalization for layout — with implementation.
App Dev2026-06-25
Three Builds on One iPhone: Environment Separation for Rork (Expo) Apps
Split a Rork-generated Expo app into dev, staging, and production builds that live side by side on one device. A hands-on walkthrough of dynamic app.config.ts, eas.json profiles, and isolating notifications, analytics, and billing per environment.
App Dev2026-06-03
Unifying Onboarding Across Six Wallpaper Apps: What One Month of First-Day Retention Showed Me
I folded the onboarding flows of six wallpaper apps scaffolded with Rork into a single config-driven component and watched first-day retention and push opt-in for a month. Here is an honest, operational note on what moved and what didn't.
📚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 →