From 20c60d9d6317e29d975314c208685a89e7d98185 Mon Sep 17 00:00:00 2001 From: Marcin Szalski Date: Thu, 7 May 2026 16:38:55 +0200 Subject: [PATCH 1/3] fix: make sure native libs are loaded for RN >= 0.80 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cause: loadNativeLibs() in ReactNativeBrownfield.kt is a no-op for RN >= 0.80. All ReactNativeHostManager templates call loadReactNative(application) BEFORE ReactNativeBrownfield.initialize(), so when done through the recommended path the libs load. But loadNativeLibs() — called inside every initialize() overload — silently skips for RN >= 0.80. Any consumer using ReactNativeBrownfield.initialize() directly — with a custom ReactHost, custom packages list, or custom options — skipped native lib loading entirely. Without JNI_OnLoad firing, registerComponentDescriptorsFromEntryPoint and the DefaultTurboModuleManagerDelegate module providers were never assigned, leading to the SIGSEGV during Fabric initialization. ddc011c — moved SoLoader.init() from preloadReactNative() into initialize(application, rnHost, ...). Before this, SoLoader.init() was called inside preloadReactNative after creating the React context. It was consolidated into the initialize entry point. 9908b68 — added the RN_THRESHOLD_VERSION = "0.80.0" gate and removed SoLoader.init() + load() for RN ≥ 0.80, leaving those calls only for < 0.80. ReactNativeBrownfield.initialize() itself had no fallback for the RN ≥ 0.80 case. If anything bypasses ReactNativeHostManager — a custom ReactHost, a consumer calling initialize() directly, or the generated ReactNativeApplicationEntryPoint being unavailable — native libs are never loaded. That's the bug this fix addresses. --- .../android/BrownfieldLib/consumer-rules.pro | 1 + .../brownfieldlib/ReactNativeHostManager.kt | 3 --- .../ReactNativeBrownfield.kt | 26 +++++++++++++++++++ .../android/ReactNativeHostManager.post55.kt | 3 --- .../android/ReactNativeHostManager.pre55.kt | 3 --- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/RNApp/android/BrownfieldLib/consumer-rules.pro b/apps/RNApp/android/BrownfieldLib/consumer-rules.pro index e69de29b..bd7e0ab1 100644 --- a/apps/RNApp/android/BrownfieldLib/consumer-rules.pro +++ b/apps/RNApp/android/BrownfieldLib/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.facebook.react.ReactNativeApplicationEntryPoint { *; } diff --git a/apps/RNApp/android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/ReactNativeHostManager.kt b/apps/RNApp/android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/ReactNativeHostManager.kt index 9e51c8c1..825698be 100644 --- a/apps/RNApp/android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/ReactNativeHostManager.kt +++ b/apps/RNApp/android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/ReactNativeHostManager.kt @@ -4,12 +4,9 @@ import android.app.Application import com.callstack.reactnativebrownfield.OnJSBundleLoaded import com.callstack.reactnativebrownfield.ReactNativeBrownfield import com.facebook.react.PackageList -import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative object ReactNativeHostManager { fun initialize(application: Application, onJSBundleLoaded: OnJSBundleLoaded? = null) { - loadReactNative(application) - val packageList = PackageList(application).packages ReactNativeBrownfield.initialize(application, packageList, onJSBundleLoaded) } diff --git a/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index 22f3387a..76959312 100644 --- a/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -41,17 +41,41 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) { companion object { private lateinit var instance: ReactNativeBrownfield private val initialized = AtomicBoolean() + private val nativeLibsLoaded = AtomicBoolean() private const val LOG_TAG = "ReactNativeBrownfield" @JvmStatic val shared: ReactNativeBrownfield get() = instance private fun loadNativeLibs(application: Application) { + if (!nativeLibsLoaded.getAndSet(true)) { + loadNativeLibsInternal(application) + } + } + + private fun loadNativeLibsInternal(application: Application) { val rnVersion = BuildConfig.RN_VERSION if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) { SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) load() + } else { + try { + val reactNativeApplicationEntryPointClazz = + Class.forName("com.facebook.react.ReactNativeApplicationEntryPoint") + val loadReactNativeMethod = reactNativeApplicationEntryPointClazz.getMethod( + "loadReactNative", + android.content.Context::class.java + ) + loadReactNativeMethod.invoke(null, application.applicationContext) + } catch (e: ClassNotFoundException) { + throw RuntimeException( + "ReactNativeApplicationEntryPoint not found. Ensure the brownfield AAR " + + "consumer-rules.pro is applied and ReactNativeApplicationEntryPoint is " + + "not stripped by R8." + e + ) + } } } @@ -79,6 +103,8 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) { options: HashMap, onJSBundleLoaded: OnJSBundleLoaded? = null ) { + loadNativeLibs(application) + val reactHost: ReactHost by lazy { getDefaultReactHost( context = application, diff --git a/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.post55.kt b/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.post55.kt index f7db6bef..57f628ba 100644 --- a/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.post55.kt +++ b/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.post55.kt @@ -6,14 +6,11 @@ import com.callstack.reactnativebrownfield.OnJSBundleLoaded import com.callstack.reactnativebrownfield.ReactNativeBrownfield import com.facebook.react.PackageList import com.facebook.react.ReactHost -import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ExpoReactHostFactory object ReactNativeHostManager { fun initialize(application: Application, onJSBundleLoaded: OnJSBundleLoaded? = null) { - loadReactNative(application) - ApplicationLifecycleDispatcher.onApplicationCreate(application) val reactHost: ReactHost by lazy { diff --git a/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt b/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt index 67688768..c577aa09 100644 --- a/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt +++ b/packages/react-native-brownfield/src/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt @@ -6,7 +6,6 @@ import com.callstack.reactnativebrownfield.OnJSBundleLoaded import com.callstack.reactnativebrownfield.ReactNativeBrownfield import com.facebook.react.PackageList import com.facebook.react.ReactHost -import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative import com.facebook.react.ReactPackage import com.facebook.react.defaults.DefaultReactNativeHost import expo.modules.ApplicationLifecycleDispatcher @@ -15,8 +14,6 @@ import expo.modules.ReactNativeHostWrapper object ReactNativeHostManager { fun initialize(application: Application, onJSBundleLoaded: OnJSBundleLoaded? = null) { - loadReactNative(application) - ApplicationLifecycleDispatcher.onApplicationCreate(application) val reactNativeHost = ReactNativeHostWrapper( From 2a80a440a36bea84be2499c1a9d8e6f7b8ae4dfa Mon Sep 17 00:00:00 2001 From: Marcin Szalski Date: Thu, 7 May 2026 16:51:59 +0200 Subject: [PATCH 2/3] docs: add changeset --- .changeset/early-rice-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/early-rice-fly.md diff --git a/.changeset/early-rice-fly.md b/.changeset/early-rice-fly.md new file mode 100644 index 00000000..8ca41852 --- /dev/null +++ b/.changeset/early-rice-fly.md @@ -0,0 +1,5 @@ +--- +'@callstack/react-native-brownfield': patch +--- + +fix: make sure native libs are loaded for RN >= 0.80 From 1d69e5da22045bfd01bfc051438ede3eabe64d9c Mon Sep 17 00:00:00 2001 From: Marcin Szalski Date: Thu, 7 May 2026 17:24:31 +0200 Subject: [PATCH 3/3] fix: address compilation issue --- .../callstack/reactnativebrownfield/ReactNativeBrownfield.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index 76959312..118b2bb7 100644 --- a/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -72,7 +72,7 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) { throw RuntimeException( "ReactNativeApplicationEntryPoint not found. Ensure the brownfield AAR " + "consumer-rules.pro is applied and ReactNativeApplicationEntryPoint is " + - "not stripped by R8." + "not stripped by R8.", e ) }