Skip to content

fix(android): stop isDebug() reaching ReactNativeHost (fatal on New Architecture) - #8341

Open
ninjz wants to merge 2 commits into
wix:masterfrom
ninjz:fix/isdebug-new-architecture
Open

fix(android): stop isDebug() reaching ReactNativeHost (fatal on New Architecture)#8341
ninjz wants to merge 2 commits into
wix:masterfrom
ninjz:fix/isdebug-new-architecture

Conversation

@ninjz

@ninjz ninjz commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Context.isDebug() reads useDeveloperSupport off the legacy ReactNativeHost:

fun Context.isDebug(): Boolean {
    return (applicationContext as ReactApplication).reactNativeHost.useDeveloperSupport
}

Under the New Architecture that host no longer exists — an app overrides reactHost instead, and ReactApplication.getReactNativeHost()'s default getter throws by design:

// com.facebook.react.ReactApplication
throw RuntimeException("You should not use ReactNativeHost directly in the New Architecture")

So on New Arch every call to isDebug() is fatal. This replaces the body with a check that needs no React APIs at all.

How it's actually hit: icons loaded by URI

The crash is easy to miss because it is gated behind a short-circuit in ImageLoader.getDrawable:

drawable = loadResource(context, source)
if (drawable == null && context.isDebug()) {   // only evaluated when the icon is NOT a bundled drawable
    drawable = readJsDevImage(context, source)
}

context.isDebug() is only reached when loadResource() fails — i.e. when the icon is not bundled in the APK as an Android drawable resource, and has to be fetched from the Metro dev server instead. That is exactly what happens for icons supplied by URI (require('./icon.png') resolved through Metro in a debug build).

That produces a confusing split:

build where tab icons come from loadResource() isDebug() called? result
release bundled into the APK as drawables returns a Drawable no works
debug + Metro served over the Metro dev server returns null yes crash

So apps that bundle their tab icons as drawable resources never see this, and release builds never see it. An app on New Arch that loads icons by URI hard-crashes the moment a bottomTabs root is set — which makes everything behind the tab bar unreachable in development.

Stack trace (react-native-navigation 8.8.11, react-native 0.85.3, newArchEnabled=true, Android API 36):

FATAL EXCEPTION: main
java.lang.RuntimeException: You should not use ReactNativeHost directly in the New Architecture
    at com.facebook.react.ReactApplication.getReactNativeHost(ReactApplication.kt:20)
    at com.reactnativenavigation.utils.ContextKt.isDebug(Context.kt:10)
    at com.reactnativenavigation.utils.ImageLoader.getDrawable(ImageLoader.kt:69)
    at com.reactnativenavigation.utils.ImageLoader.loadIcon(ImageLoader.kt:34)
    at com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController.lambda$createTabs$3(BottomTabsController.java:297)
    at com.reactnativenavigation.utils.CollectionUtils.map(CollectionUtils.java:62)
    at com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController.createTabs(BottomTabsController.java:293)
    at com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController.createView(BottomTabsController.java:119)
    at com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController.getView(ViewController.java:240)
    at com.reactnativenavigation.viewcontrollers.viewcontroller.RootPresenter.setRoot(RootPresenter.java:43)
    at com.reactnativenavigation.viewcontrollers.navigator.Navigator.setRoot(Navigator.java:155)
    at com.reactnativenavigation.react.NavigationTurboModule.setRoot$lambda$0(NavigationTurboModule.kt:92)

The tab-icon path is just the one that detonates first. isDebug() has four call sites, all equally fatal on New Arch, which is why this fixes the function rather than the caller:

File Line Context
utils/Context.kt 9 the definition
utils/ImageLoader.kt 69 getDrawable — the tab-icon crash
utils/ImageLoader.kt 97 readJsDevImage
utils/ImageLoader.kt 105 adjustThreadPolicyDebug
viewcontrollers/viewcontroller/YellowBoxDelegate.kt 26 onChildViewAdded early-return

The change

 import android.content.Context
+import android.content.pm.ApplicationInfo
 import android.content.res.Configuration
 import androidx.appcompat.app.AppCompatDelegate
-import com.facebook.react.ReactApplication
 import com.reactnativenavigation.NavigationApplication
 
 fun Context.isDebug(): Boolean {
-    return (applicationContext as ReactApplication).reactNativeHost.useDeveloperSupport
+    return (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
 }

FLAG_DEBUGGABLE answers the same question the three call sites are actually asking — "is this a development build, so is it OK to fetch an image from a dev server / relax StrictMode / expect LogBox views?" — without depending on either React host. It works identically on both architectures, so no branching is required.

It also removes the last use of com.facebook.react.ReactApplication in this file, so the import goes with it.

Behaviour: for a standard setup useDeveloperSupport defaults to the debug build type, which is exactly what FLAG_DEBUGGABLE reports — so this is equivalent on Old Arch and correct rather than fatal on New Arch. The one nuance worth flagging: an app that deliberately overrode getUseDeveloperSupport() to something other than BuildConfig.DEBUG would now see isDebug() follow the manifest's debuggable flag instead. Happy to switch to reading reactHost.devSupportEnabled with a fallback if you'd prefer to preserve that, though it's a good deal more code for a case these three call sites don't really care about.

iOS is unaffectedisDebug exists only in the Android source. ImageParser.mm hands URI icons to [RCTConvert UIImage:], which already resolves dev-server URLs, so iOS never needs the check.

Verification

Tested on a real app (react-native 0.85.3, RNN 8.8.11, New Arch, Android API 36 emulator), same commit both ways with the patch as the only variable:

  • without this changeFATAL EXCEPTION on setRoot as the tab bar is constructed, process dies, 100% reproducible
  • with this change — tabs root commits, all three tabs navigate, and the icons render

The icons rendering is the part that confirms the replacement is correct and not merely non-fatal: they only appear if isDebug() returned true and sent the loader down readJsDevImage() to fetch them from Metro. A wrong-but-safe implementation would have produced blank icons instead.

Release builds were verified unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant