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

Copy link
Copy Markdown

Summary

Context.isDebug() reads useDeveloperSupport off the legacyReactNativeHost:

fun Context.isDebug(): Boolean {
return (applicationContext asReactApplication).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.ReactApplicationthrowRuntimeException("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:

buildwhere tab icons come fromloadResource()isDebug() called?result
releasebundled into the APK as drawablesreturns a Drawablenoworks
debug + Metroserved over the Metro dev serverreturns nullyescrash

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:

FileLineContext
utils/Context.kt9the definition
utils/ImageLoader.kt69getDrawable — the tab-icon crash
utils/ImageLoader.kt97readJsDevImage
utils/ImageLoader.kt105adjustThreadPolicyDebug
viewcontrollers/viewcontroller/YellowBoxDelegate.kt26onChildViewAdded 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 freeto 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

@ninjz