◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-10-18Intermediate

It Ran Fine in Debug, but the Release Build Died on Launch with a Resource Not Found. A File I Looked Up by Name Had Been Shrunk Away

A release-only crash at launch, caused by images and sounds looked up by a name string being removed by resource shrinking. A keep.xml written at prebuild, plus a post-build check that confirms it worked.

Expo220Android56Resource Shrinkingkeep.xmlTroubleshooting46

✦ Premium Article

Debug launched cleanly every time. The moment I switched to a release build, the Android version of one of my wallpaper apps stopped right after launch. The log held a single line, roughly "not found".

The code was identical to what had worked in debug. Only the build type differed. I suspected signing and obfuscation first, and dug in the wrong places for a while.

"It worked in debug" is not evidence that it works in release. This crash was a textbook case. As an indie developer I hit this symptom in a real app; this note rewrites it for an Expo project, covering how to tell the cause apart, how to fix it, and a check that keeps it from coming back.

Decide what to suspect from the symptom

A release-only crash usually falls into one of two groups: code that was shrunk away, or resources (images, sounds, XML) that were shrunk away. The message in the crash log tells them apart.

What the log saysSuspectFirst move
ClassNotFoundException, NoSuchMethodErrorCode shrinking (R8)Look the name up in mapping.txt and add a keep rule
Resources$NotFoundException, a resource ID of 0Resource shrinkingLook for resources fetched by a name string
Neither, and a white screenThe JS bundleOut of scope for this note

The code side is covered in the note on four numbers to record before R8 becomes the default. This note is about the second row.

Why debug never shows it

Resource shrinking only runs in release builds, and only when code shrinking is on. Debug builds skip it, so resources that look unused are simply left in. In other words, debug behavior has never passed through the shrinking step at all.

The shrinker statically looks for referenced resources in your code. A constant reference such as R.raw.notify_chime is found. What it cannot see is a name assembled at runtime and passed in as a string.

The official documentation says that a format string such as img_%1d makes the shrinker treat every resource starting with img_ as used. The flip side is that a name received from a server response or a config file leaves no string constant in the code to hint at it, so nothing protects it.

The crash in my app was the same kind. A resource that existed in debug was judged unnecessary and removed in release, and the app stopped at launch with a "not found". The fix was to protect the resources I did not want removed with an explicit keep.

✦

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
✦You can now list every image and sound in your Expo project that is looked up by a name string, and check before shipping whether any of them sit on the side that release builds remove
✦You can now write a keep.xml from a config plugin that survives every prebuild, so you keep resource shrinking on and protect only the resources you name
✦You can now catch a failure that never reproduces in debug with a single post-build check, instead of launching a release build on one device after another
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-10-15
When Only EAS Build Fails with "No matching variant", Check Your Local android/build First
Gradle passes on your machine, but the Android build on EAS stops at "No matching variant of project". Reading the reports on GitHub, I traced how a Windows-generated autolinking cache can reach the Linux builder, and wrote a small script that checks for it before you queue a build.
◇ App Dev2026-09-23
It Crashed, but the Reviews Say "It Freezes" — When an Android Crash Leaves a Blank Screen Behind
Your crash reports show exceptions, yet users only ever say the app freezes. Here is why expo-updates plus the New Architecture can leave an empty Android screen behind, and the order I now use: a way out first, a bad-exit marker second, root cause last.
◇ App Dev2026-09-16
The Token I Deleted Came Back After a Restart — Detecting Failed SecureStore Deletes
On Android, getItemAsync can report null right after sign-out while the value is still sitting on disk. Here is how I rewrote a sign-out path that was verifying deletion by reading instead of by the delete result.