RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
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.

Rork540Expo176first 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-08-21
Apple's automated pass reads what your purpose strings are for, not whether they exist
Submit a Rork or Expo iOS build and you may get it back under Guideline 5.1.1 with a note about placeholder or otherwise insufficient purpose strings. Here is how to read a rejection that names no key, and how to rewrite the strings from app.json.
App Dev2026-08-18
The three places I had to fix before a Rork project actually targeted API level 36
My app.json said targetSdkVersion 36. The value my build actually read was 35. Here is the script that reports the effective value, and how I split my apps between raising, leaving alone, and requesting an extension.
App Dev2026-08-16
My chart broke on day one, not at scale
A line chart that vanished for anyone with only a few days of data. The cause was a zero-height Y axis turning coordinates into NaN. Here is the measured behavior and the small normalization layer that fixed it.
📚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 →