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-25Intermediate

Crashes Only in the Release Build — Rescuing Classes R8 Stripped in Expo (Android)

I turned on R8 code shrinking to slim down an AAB, and one screen started crashing only in production. Here is how I traced the stripped class through mapping.txt and added keep rules via expo-build-properties.

Expo102Android43R8ProGuardTroubleshooting35

An app that had already cleared review and gone live started drawing a single crash report: it died the moment one particular screen opened. On my machine it never reproduced. Expo Go was fine. The expo run:android debug build was fine. The only build that crashed was the production AAB downloaded from Play.

The cause was a change I had made just before shipping. Wanting to shave a little off the download size, I had enabled R8 code shrinking. R8 decided a class was unreferenced and removed it, and at runtime the app went looking for that class, failed to find it, and crashed. To save you the same detour, here is how to recognize the symptom and apply keep rules.

The symptom: fine in debug, dead only in production

This crash has a distinct fingerprint.

It never reproduces during development. The debug build runs with minifyEnabled off, so the code stays intact and there is nothing to strip. Shrinking only happens in the release build, so the symptom only shows up there.

In the crash log, the exception surfaces as a ClassNotFoundException, a NoSuchMethodError, or sometimes a Kotlin NullPointerException. The class names in the stack trace are collapsed into short tokens like a.a.b. That obfuscated naming is the telltale sign that R8 touched the code.

It also tends to crash on one specific screen or feature rather than the whole app — concentrated wherever you use reflection or JSON deserialization.

Why a class you clearly use disappears

R8 walks your code statically to decide what is "reachable." It can follow ordinary method calls, but it cannot see a class resolved by name through reflection, a bridge invoked from native code, or a model class that a JSON library fills by matching field names. To the static analyzer, those look like nobody calls them.

So a class or field that is genuinely needed at runtime gets treated as unused — deleted, or renamed to something short. When a library like Gson relies on field names, a renamed field no longer matches, and you get empty values or an exception.

In other words, the code is not wrong; R8 simply was not told the part is needed. The fix is entirely about declaring, with keep rules, what must survive.

Trace the stripped class through mapping.txt

Before adding keep rules at random, pin down what actually got removed. Every build, R8 emits a mapping table for its obfuscation.

android/app/build/outputs/mapping/release/mapping.txt

This file maps "obfuscated name to original name." If you build with EAS Build, you can pull the same mapping.txt from the build artifacts.

Look up the a.a.b-style token from the device stack trace in mapping.txt and you recover the original class name. If you use Google Play Console, upload that mapping.txt against the app version, and the crash reports in Console will be de-obfuscated back to readable names automatically. I forgot this step once and spent a good half hour staring at a wall of symbols.

Once you have the real class name, ask why it was invisible to static analysis. Usually it is reflection, or a serialization model.

Apply keep rules with expo-build-properties

In Expo's managed workflow, editing android/ directly gets overwritten by prebuild. R8 settings and keep rules go through the expo-build-properties plugin.

To preserve a group of stripped model classes, keep them by package.

{
  "expo": {
    "plugins": [
      [
        "expo-build-properties",
        {
          "android": {
            "enableProguardInReleaseBuilds": true,
            "enableShrinkResourcesInReleaseBuilds": true,
            "extraProguardRules": "-keepattributes Signature\n-keepattributes *Annotation*\n-keep class com.yourapp.models.** { *; }"
          }
        }
      ]
    ]
  }
}

Replace com.yourapp.models.** with your app's real package name. The two -keepattributes lines matter when Gson and similar libraries read generics or annotations.

If you restore enums from strings anywhere, keeping values() and valueOf() is the safe move.

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

If a bridge class is called by name from native code or a library, keep it explicitly too.

-keep class com.yourapp.NativeBridge { *; }

After writing the rules, regenerate android/ with npx expo prebuild --clean and run a release build. In my case, keeping the model package stopped the crash on that screen. The nice part was not having to abandon shrinking altogether.

Guarding against a repeat

Right after enabling R8, always take one full pass through a release build on a real device. Build the same AAB you would ship with --local, install it, and open your main screens in order — that alone catches almost every strip-related crash before users do.

mapping.txt changes every build, so keep the one for each released version, or upload it to Play Console. Without it you are reading production crashes in their obfuscated form, which makes diagnosis noticeably heavier.

It is tempting to keep everything with a broad -keep class com.yourapp.** { *; }, but that dilutes the shrinking. Keeping only the range that was actually stripped gets you closer to both smaller size and stability.

For app size reduction itself, my practical guide to shrinking a Rork app bundle covers the moves beyond R8. Reading both should make it easier to draw the line between what to cut and what to keep.

As a next step, if any app you currently ship has enableProguardInReleaseBuilds on, build a --local release locally and walk through the main screens. If a strip is lurking, you will find it before your users do.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-01
EAS Update Published but Nothing Changes? Five Patterns That Quietly Break OTA Delivery in Rork
You ran eas update, the CLI showed a green Published, but your iPhone keeps loading the old code. Here are the five patterns I keep running into, plus a five-minute diagnostic flow you can use the next time OTA goes silent.
Dev Tools2026-04-29
Why Your Rork Android App Shows a White Square Notification Icon (and How to Fix It)
Your Rork or Expo app's notification icon shows up as a white square or blob on Android. Here's the underlying Android spec, the correct transparent icon recipe, the right app.json fields, and the cache traps that make fixes appear to do nothing.
Dev Tools2026-04-25
Why Your Rork App Icon Won't Update — Cache Fixes for iOS and Android
Replaced your Rork app icon but still seeing the old one? Walk through the layered causes — iOS SpringBoard cache, EAS Build cache, missing Android adaptive icons — and the exact fixes that get the new icon to stick.
📚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 →