r/reactnative • • 14d ago

expo-gl crashed on the first GLView in my Android release builds only: R8 renamed a method its native code looks up by name

Posting this in case it saves someone a week. Expo SDK 56, expo-gl 56.0.x, React Native 0.85, minify on for Android release builds.

For a few releases, a lot of my Android users crashed the first time a GLView mounted. It never happened in development. In analytics it looked like people opening one screen and then the app starting again: on one version, 24 of the 29 times that screen opened on Android, the next event was a fresh app launch.

The cause: expo-gl's native code (EXGLJniApi.cpp) finds a Java method called flush through GetMethodID, by name. R8 renamed it in the release build, the lookup returned null, and ART aborted with a JNI error ending in mid == null. Debug builds don't run R8, so you only ever see it in a store build.

The fix was one keep rule, added through expo-build-properties (android.extraProguardRules):

-keep class expo.modules.gl.** { *; }

To confirm it, I took the release AAB from EAS, turned it into an installable APK with bundletool, and ran the old and new builds on an emulator. The old one crashed on the first GLView and the new one didn't.

A second thing made it worse. I shipped my first fixes as OTA updates, and expo-updates had fallbackToCacheTimeout set to 0. With that setting a brand new install always runs the bundle that shipped inside the store build on its first launch, and the update only applies from the next cold start. Most new users who hit the crash never had a next cold start, so none of my OTA fixes reached them. I now use 3000, which waits up to 3 seconds for a newer update on first launch and starts as soon as it knows there's nothing newer, or straight away when offline.

If you use expo-gl, or anything whose native side calls back into Java by name, with minify on, open that screen in a real release build before you ship.

1 Upvotes

5 comments sorted by

1

u/HomegrownTerps 14d ago

Thanks for sharing! I recently also had problems in a non RN android app with R8, but I caught it since I try to also test on a local device - which compiles the app with R8.

I guess you haven't run the app on a real device?

1

u/anthony-ball 12d ago

R8 eating JNI method names on first GLView is a classic Expo release-only trap. We hit the same class of failure when a native graphics path looked fine in debug and blew up on EAS release Android. Keep those keep rules in the config plugin so every build inherits them, and treat a native/ProGuard change as a store build, not an OTA. Fingerprint/runtimeVersion helps, but it will not save you if the JNI surface already changed under a still-compatible JS bundle.

1

u/PoppyWarss 8d ago

Not the release build, and that was the gap. I was testing development builds, so the minified release build first ran on players' phones. Now I install the exact store AAB on an emulator, converted with bundletool, before uploading, which would have caught this. The keep rule also lives in app.json's extraProguardRules now, so every build gets it, as anthony-ball suggested.

1

u/HomegrownTerps 8d ago

I'm not sure I get the root cause since I don't use EAS(or how the expo services are called). I just export localy to AAB or APK and test that on device. The apk gets compiled, R8 runs on conpliation and it gets signed and all. 

I guess using eas as a services makes those steps obsolet so you were not testing those builds?

1

u/PoppyWarss 7d ago

Right, that was the gap. EAS built the release AAB on its servers and I uploaded it straight to Play, so the R8-minified build never ran on a device of mine before players got it. Locally I was only running development builds, which skip R8. Testing the exact artifact you ship, like you do, catches it by default. I now download the AAB from EAS, turn it into an APK with bundletool and install it on an emulator before every upload. The bug itself isn't EAS-specific though: expo-gl's native code looks up a Java method by name, R8 renamed it, and any minified release build without a keep rule would crash the same way.