From ddc011c53d11dc37d0154102537540e8a861f04c Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Thu, 17 Jul 2025 16:18:51 +0500 Subject: [PATCH 1/7] feat: combine loading react native in initialize function --- .../ReactNativeBrownfield.kt | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index 160c7770..f0b575f1 100644 --- a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost -interface InitializedCallback { +fun interface InitializedCallback { operator fun invoke(initialized: Boolean) } @@ -34,15 +34,19 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative val shared: ReactNativeBrownfield get() = instance @JvmStatic - fun initialize(application: Application, rnHost: ReactNativeHost) { + fun initialize(application: Application, rnHost: ReactNativeHost, callback: InitializedCallback? = null) { if (!initialized.getAndSet(true)) { instance = ReactNativeBrownfield(rnHost) SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) + + preloadReactNative { + callback?.invoke(true) + } } } @JvmStatic - fun initialize(application: Application, options: HashMap) { + fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(application) { @@ -61,37 +65,31 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - initialize(application, reactNativeHost) + initialize(application, reactNativeHost, callback) } @JvmStatic - fun initialize(application: Application, packages: List) { + fun initialize(application: Application, packages: List, callback: InitializedCallback? = null) { val options = hashMapOf("packages" to packages, "mainModuleName" to "index") - initialize(application, options) + initialize(application, options, callback) } + private fun preloadReactNative(callback: ((Boolean) -> Unit)) { + val reactInstanceManager = shared.reactNativeHost.reactInstanceManager + reactInstanceManager.addReactInstanceEventListener(object : + ReactInstanceEventListener { + override fun onReactContextInitialized(reactContext: ReactContext) { + callback(true) + reactInstanceManager.removeReactInstanceEventListener(this) + } + }) + reactInstanceManager?.createReactContextInBackground() - } - - fun startReactNative(callback: InitializedCallback?) { - startReactNative { callback?.invoke(it) } - } - - @JvmName("startReactNativeKotlin") - fun startReactNative(callback: ((initialized: Boolean) -> Unit)?) { - reactNativeHost.reactInstanceManager.addReactInstanceEventListener(object : - ReactInstanceEventListener { - override fun onReactContextInitialized(reactContext: ReactContext) { - callback?.let { it(true) } - reactNativeHost.reactInstanceManager.removeReactInstanceEventListener(this) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() } - }) - reactNativeHost.reactInstanceManager?.createReactContextInBackground() - - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() } } From 9908b68dec7f8e30decf86947bf0577eff62d0fa Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Fri, 18 Jul 2025 13:30:14 +0500 Subject: [PATCH 2/7] feat: add RN 0.80.0 support and mark other initialize overloads as private --- android/build.gradle | 13 ++++++++ .../ReactNativeBrownfield.kt | 31 ++++++++++++------- .../utils/VersionUtils.kt | 17 ++++++++++ .../kotlinexample/MainApplication.kt | 3 +- 4 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 android/src/main/java/com/callstack/reactnativebrownfield/utils/VersionUtils.kt diff --git a/android/build.gradle b/android/build.gradle index 4876d1c4..d3850b23 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,15 @@ +import groovy.json.JsonSlurper + +def reactNativeVersion = null +def packageJsonFile = rootProject.file("../node_modules/react-native/package.json") + +if (packageJsonFile.exists()) { + def json = new JsonSlurper().parse(packageJsonFile) + reactNativeVersion = json.version +} else { + reactNativeVersion = "unknown" +} + buildscript { // Buildscript is evaluated before everything else so we can't use getExtOrDefault def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["RNBrownfield_kotlinVersion"] @@ -64,6 +76,7 @@ android { targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() buildConfigField "boolean", "IS_HERMES_ENABLED", isHermesEnabled().toString() + buildConfigField "String", "RN_VERSION", "\"$reactNativeVersion\"" } buildFeatures { diff --git a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index f0b575f1..4b427898 100644 --- a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -8,6 +8,7 @@ import androidx.activity.OnBackPressedCallback import androidx.fragment.app.FragmentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import com.callstack.reactnativebrownfield.utils.VersionUtils import com.facebook.react.ReactInstanceEventListener import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactNativeHost @@ -25,6 +26,13 @@ fun interface InitializedCallback { operator fun invoke(initialized: Boolean) } +/** + * The threshold RN version based on which we decide whether to + * load JNI libs or not. We only load JNI libs on version less + * than this. + */ +private const val RN_THRESHOLD_VERSION = "0.80.0" + class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNativeHost) { companion object { private lateinit var instance: ReactNativeBrownfield @@ -33,11 +41,9 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative @JvmStatic val shared: ReactNativeBrownfield get() = instance - @JvmStatic - fun initialize(application: Application, rnHost: ReactNativeHost, callback: InitializedCallback? = null) { + private fun initialize(rnHost: ReactNativeHost, callback: InitializedCallback? = null) { if (!initialized.getAndSet(true)) { instance = ReactNativeBrownfield(rnHost) - SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) preloadReactNative { callback?.invoke(true) @@ -45,8 +51,7 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative } } - @JvmStatic - fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { + private fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(application) { @@ -65,12 +70,21 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - initialize(application, reactNativeHost, callback) + initialize(reactNativeHost, callback) } @JvmStatic fun initialize(application: Application, packages: List, callback: InitializedCallback? = null) { val options = hashMapOf("packages" to packages, "mainModuleName" to "index") + val rnVersion = BuildConfig.RN_VERSION + + if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) { + SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + } initialize(application, options, callback) } @@ -85,11 +99,6 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative } }) reactInstanceManager?.createReactContextInBackground() - - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } } } diff --git a/android/src/main/java/com/callstack/reactnativebrownfield/utils/VersionUtils.kt b/android/src/main/java/com/callstack/reactnativebrownfield/utils/VersionUtils.kt new file mode 100644 index 00000000..e9da5545 --- /dev/null +++ b/android/src/main/java/com/callstack/reactnativebrownfield/utils/VersionUtils.kt @@ -0,0 +1,17 @@ +package com.callstack.reactnativebrownfield.utils + +object VersionUtils { + fun isVersionLessThan(version: String, threshold: String): Boolean { + val versionParts = version.split(".").map { it.toIntOrNull() ?: 0 } + val thresholdParts = threshold.split(".").map { it.toIntOrNull() ?: 0 } + + val maxLength = maxOf(versionParts.size, thresholdParts.size) + for (i in 0 until maxLength) { + val vPart = versionParts.getOrNull(i) ?: 0 + val tPart = thresholdParts.getOrNull(i) ?: 0 + if (vPart != tPart) return vPart < tPart + } + + return false // equal versions are not less than + } +} \ No newline at end of file diff --git a/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainApplication.kt b/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainApplication.kt index e5f38348..926068dd 100644 --- a/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainApplication.kt +++ b/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainApplication.kt @@ -10,8 +10,7 @@ class MainApplication : Application() { super.onCreate() val packages = PackageList(this).packages - ReactNativeBrownfield.initialize(this, packages) - ReactNativeBrownfield.shared.startReactNative { + ReactNativeBrownfield.initialize(this, packages) { Log.d("test", "test") } } From ebabee92bbddc7d08ad982e327136498f4b6ea91 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Fri, 18 Jul 2025 13:48:08 +0500 Subject: [PATCH 3/7] feat: revert private from initialize overloads --- .../ReactNativeBrownfield.kt | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index 4b427898..19ce82fa 100644 --- a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -41,8 +41,22 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative @JvmStatic val shared: ReactNativeBrownfield get() = instance - private fun initialize(rnHost: ReactNativeHost, callback: InitializedCallback? = null) { + private fun loadNativeLibs (application: Application) { + val rnVersion = BuildConfig.RN_VERSION + + if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) { + SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + } + } + + @JvmStatic + fun initialize(application: Application, rnHost: ReactNativeHost, callback: InitializedCallback? = null) { if (!initialized.getAndSet(true)) { + loadNativeLibs(application) instance = ReactNativeBrownfield(rnHost) preloadReactNative { @@ -51,7 +65,8 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative } } - private fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { + @JvmStatic + fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(application) { @@ -70,21 +85,12 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - initialize(reactNativeHost, callback) + initialize(application, reactNativeHost, callback) } @JvmStatic fun initialize(application: Application, packages: List, callback: InitializedCallback? = null) { val options = hashMapOf("packages" to packages, "mainModuleName" to "index") - val rnVersion = BuildConfig.RN_VERSION - - if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) { - SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } - } initialize(application, options, callback) } From 826a9353255a156a0260431fa359b9d6e7947dee Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Fri, 18 Jul 2025 14:18:59 +0500 Subject: [PATCH 4/7] docs: update docs with recent changes --- .../ReactNativeBrownfield.kt | 17 +++++--- docs/JAVA.md | 43 ++++++++----------- docs/KOTLIN.md | 43 ++++++++----------- 3 files changed, 48 insertions(+), 55 deletions(-) diff --git a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt index 19ce82fa..ae92d73f 100644 --- a/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +++ b/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt @@ -22,7 +22,7 @@ import java.util.concurrent.atomic.AtomicBoolean import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost -fun interface InitializedCallback { +fun interface OnJSBundleLoaded { operator fun invoke(initialized: Boolean) } @@ -54,19 +54,21 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative } @JvmStatic - fun initialize(application: Application, rnHost: ReactNativeHost, callback: InitializedCallback? = null) { + @JvmOverloads + fun initialize(application: Application, rnHost: ReactNativeHost, onJSBundleLoaded: OnJSBundleLoaded? = null) { if (!initialized.getAndSet(true)) { loadNativeLibs(application) instance = ReactNativeBrownfield(rnHost) preloadReactNative { - callback?.invoke(true) + onJSBundleLoaded?.invoke(true) } } } @JvmStatic - fun initialize(application: Application, options: HashMap, callback: InitializedCallback? = null) { + @JvmOverloads + fun initialize(application: Application, options: HashMap, onJSBundleLoaded: OnJSBundleLoaded? = null) { val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(application) { @@ -85,14 +87,15 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - initialize(application, reactNativeHost, callback) + initialize(application, reactNativeHost, onJSBundleLoaded) } @JvmStatic - fun initialize(application: Application, packages: List, callback: InitializedCallback? = null) { + @JvmOverloads + fun initialize(application: Application, packages: List, onJSBundleLoaded: OnJSBundleLoaded? = null) { val options = hashMapOf("packages" to packages, "mainModuleName" to "index") - initialize(application, options, callback) + initialize(application, options, onJSBundleLoaded) } private fun preloadReactNative(callback: ((Boolean) -> Unit)) { diff --git a/docs/JAVA.md b/docs/JAVA.md index 336fe199..af440184 100644 --- a/docs/JAVA.md +++ b/docs/JAVA.md @@ -45,6 +45,7 @@ Params: | rnHost | No* | `ReactNativeHost` | An instance of [ReactNativeHost](https://bit.ly/2ZnwgnA). | | packages | No* | `List` | List of your React Native Native modules. | | options | No* | `HashMap` | Map of initial options. __Options listed below.__ | +| onJSBundleLoaded | No* | `OnJSBundleLoaded` | Callback invoked after JS bundle is fully loaded. | > * - Those fields aren't itself required, but at least one of them is. See examples below. @@ -78,12 +79,24 @@ private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { }; ReactNativeBrownfield.initialize(this, mReactNativeHost); + +OR + +ReactNativeBrownfield.initialize(this, mReactNativeHost, initialized -> { + // JS bundle loaded +}); ``` ```java List packages = new PackageList(this).getPackages(); ReactNativeBrownfield.initialize(this, packages); + +OR + +ReactNativeBrownfield.initialize(this, packages, initialized -> { + // JS bundle loaded +}); ``` ```java @@ -93,6 +106,12 @@ options.put("packages", packages); options.put("mainModuleName", "example/index"); ReactNativeBrownfield.initialize(this, options); + +OR + +ReactNativeBrownfield.initialize(this, options, initialized -> { + // JS bundle loaded +}); ``` --- @@ -119,30 +138,6 @@ ReactNativeBrownfield.getShared() **Methods:** -`startReactNative` - -Starts React Native, produces an instance of react native. You can use it to initialize React Native in your app. - -Params: - -| Param | Required | Type | Description | -| ----------------------- | -------- | ------------- | ----------------------------------------------------- | -| startReactNative | No | `Lambda` | Callback invoked after JS bundle is fully loaded. | - -Examples: - -```java -ReactNativeBrownfield.getShared().startReactNative(); -``` - -```java -ReactNativeBrownfield.getShared().startReactNative(init -> { - Log.d("loaded", "React Native loaded"); -}); -``` - ---- - `createView` Creates a React Native view with a given module name. It automatically uses an instance of React Native created in `startReactNative` method. This is useful when embedding React Native views directly in your native layouts. diff --git a/docs/KOTLIN.md b/docs/KOTLIN.md index c17f6631..33f9908f 100644 --- a/docs/KOTLIN.md +++ b/docs/KOTLIN.md @@ -28,6 +28,7 @@ Params: | rnHost | No* | `ReactNativeHost` | An instance of [ReactNativeHost](https://bit.ly/2ZnwgnA). | | packages | No* | `List` | List of your React Native Native modules. | | options | No* | `HashMap` | Map of initial options. __Options listed below.__ | +| onJSBundleLoaded | No* | `OnJSBundleLoaded` | Callback invoked after JS bundle is fully loaded. | > * - Those fields aren't itself required, but at least one of them is. See examples below. @@ -56,12 +57,24 @@ val mReactNativeHost = object : ReactNativeHost(application) { } ReactNativeBrownfield.initialize(this, mReactNativeHost) + +OR + +ReactNativeBrownfield.initialize(this, mReactNativeHost) { + // onJSBundleLoaded +} ``` ```kotlin val packages = PackageList(this).getPackages() ReactNativeBrownfield.initialize(this, packages) + +OR + +ReactNativeBrownfield.initialize(this, packages) { + // onJSBundleLoaded +} ``` ```kotlin @@ -72,6 +85,12 @@ val options = hashMapOf( ) ReactNativeBrownfield.initialize(this, options) + +OR + +ReactNativeBrownfield.initialize(this, options) { + // onJSBundleLoaded +} ``` --- @@ -98,30 +117,6 @@ ReactNativeBrownfield.shared **Methods:** -`startReactNative` - -Starts React Native, produces an instance of React Native. You can use it to initialize React Native in your app. - -Params: - -| Param | Required | Type | Description | -| ----------------------- | -------- | ------------- | ----------------------------------------------------- | -| startReactNative | No | `(loaded: boolean) -> Unit` | Callback invoked after JS bundle is fully loaded. | - -Examples: - -```kotlin -ReactNativeBrownfield.shared.startReactNative() -``` - -```kotlin -ReactNativeBrownfield.shared.startReactNative { - Log.d("loaded", "React Native loaded"); -} -``` - ---- - `createView` Creates a React Native view with a given module name. It automatically uses an instance of React Native created in `startReactNative` method. This is useful when embedding React Native views directly in your native layouts or Jetpack Compose UI. From 27a07833e3c4ca0fd9c276c4d0b78765ebd2a389 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Fri, 18 Jul 2025 14:34:58 +0500 Subject: [PATCH 5/7] docs: add docs for RN >= 0.80.0 --- docs/JAVA.md | 15 +++++++++++++++ docs/KOTLIN.md | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/JAVA.md b/docs/JAVA.md index af440184..fa1c3734 100644 --- a/docs/JAVA.md +++ b/docs/JAVA.md @@ -19,6 +19,21 @@ buildscript { } ``` +### React Native >= 0.80.0 (extra step) + +With react-native >= 0.80.0, an auto-generated file was added which is responsible to load your App's native libs. If you're consuming this library in a RN project, then +you will have this file `ReactNativeApplicationEntryPoint` available. If you're consuming this library in a RN android library which is backed by +`com.callstack.react:brownfield-gradle-plugin`, then this file will also be available. + +Below is the code you need to add before you call `RNBrownfield.initialize`: + +```java +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative + +loadReactNative(application); +RNBrownfield.initialize(application, packages); +``` + ### API Reference #### `ReactNativeBrownfield` diff --git a/docs/KOTLIN.md b/docs/KOTLIN.md index 33f9908f..2f747529 100644 --- a/docs/KOTLIN.md +++ b/docs/KOTLIN.md @@ -2,6 +2,21 @@ React Native Brownfield provides first-class support for Kotlin. +### React Native >= 0.80.0 (extra step) + +With react-native >= 0.80.0, an auto-generated file was added which is responsible to load your App's native libs. If you're consuming this library in a RN project, then +you will have this file `ReactNativeApplicationEntryPoint` available. If you're consuming this library in a RN android library which is backed by +`com.callstack.react:brownfield-gradle-plugin`, then this file will also be available. + +Below is the code you need to add before you call `RNBrownfield.initialize`: + +```kt +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative + +loadReactNative(application); +RNBrownfield.initialize(application, packages); +``` + ### API Reference #### `ReactNativeBrownfield` From e3667d43c139bbcad4ff4f4654017f0f95e12285 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Fri, 18 Jul 2025 15:51:17 +0500 Subject: [PATCH 6/7] docs: add brownfield guidelines --- docs/GUIDELINES.md | 133 +++++++++++++++++++++++++++++++++++++++++++++ docs/KOTLIN.md | 4 +- 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 docs/GUIDELINES.md diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md new file mode 100644 index 00000000..942d5016 --- /dev/null +++ b/docs/GUIDELINES.md @@ -0,0 +1,133 @@ +Here you can find the guidelines for standard brownfield approach and advanced use cases. + +### Standard Brownfield + +As a golden rule of standard brownfield with react-native, your native App should never have to interact directly +with react-native APIs. There are various reasons that doing the opposite is discouraged. + +- If you have different teams working on RN brownfield and the native App and you distribute your AAR/XCFramework to the native +team. They can use the APIs from those artifacts and interact with them. However, if the native team have to import a react native +API, say `PackageList` then the rule of brownfield is being violated. The native team have native developers and they should not need +to worry about and interact with react-native directly. All of the abstraction should be handled within your artifacts. + +- If your native App interacts with react-native directly then you could imagine how complicated the codebase would be. The native App +should follow and worry about their native APIs rather than interacting with react-native. If in future, some react-native APIs needs to +be changed or refactored, then the effort would be cumbersome. On the contrast, if your native App was interacting with your artifact only +then the native App need not to worry about what happens internally. This makes things simpler for the native App team. + +- If the native App team is using your artifact and any build, compile time or run time issue arises the stack trace would lead to your artifact +and making it simpler for the teams to focus on their area only. On the contrary, if the native App team would interact with react-native directly, +then any related issues would be time consuming for that team to figure out the root cause and then delegate to the team managing RN flows. + +Building upon the above points, below is how your brownfield implementation should look like if you're using `react-native-brownfield`: + +- In your brownfield android library or iOS xcframework, create a class following the facade pattern. The role of this class would be to encapsulate the +initialization of `react-native-brownfield` by not asking the native App to interact with `react-native` directly. Below is how it would look like: + +```kt +// Your artifact +import com.facebook.react.PackageList +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative + +class ReactNativeHostManager { + companion object { + fun initialize(application: Application) { + loadReactNative(application) // imported from autogenerated ReactNativeApplicationEntryPoint + val packages = PackageList(application).packages // imported from autogenerated PackageList + + ReactNativeBrownfield.initialize(application, packages) + } + } +} +``` + +Then the native App only needs to call `initialize` like so: + +```kt +// native App +ReactNativeHostManager.initialize(application) +``` + +If you do not follow this approach, then the usage in the native App would look like this: + +```kt +// native App +import com.facebook.react.PackageList +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative + + +loadReactNative(application) +val packages = PackageList(this).getPackages() +ReactNativeHostManager.initialize(application) +``` + +As you can see the issue here is that now we are mixing the native App with react-native APIs, which is discouraged. If we stick to +the above approach then the native App is free of interacting with react-native directly. + + +### Advanced Use Cases: + +If you built on top of the above approach, then you can rather easily scale it to incorporate advanced use cases. We will discuss here only one +case but you can extend it to your need as the gist remains the same. + +Consider you need to present a few native App's existing screens from react-native. Which means you need a communication way so that you can tell +the native App to present this screen. Let's see how we can achieve this: + +In your `ReactNativeHostManager` add the following method: + +```kt +// Your artifact +class ReactNativeHostManager { + companion object { + fun createView( + context: Context, + activity: FragmentActivity?, + moduleName: String, + launchOptions: Bundle? = null, + eventHandler: (String) -> Unit = {} + ): FrameLayout { + EventHandlerRegistry.register(moduleName, eventHandler) // Later invoke this event or callback to perform the navigation + return ReactNativeBrownfield.shared.createView(context, activity, moduleName, launchOptions) + } + } +} +``` + +What happens here is that the `createView` method now accepts an optional callback or eventHandler argument. The native App will rely on this eventHandler +to perform the navigation or to receive any events from the react-native side. The usage in the native App would look like below: + +```kt +// native App +ReactNativeHostManager.createView(context, activity, "Enterprise") { + if (it == "navigate_to_faq") { + // present faq fragment + } +} +``` + +HINT: To achieve this you will need to write some native code in your artifact to trigger an event from JS and then forward that event by invoking the eventHandler. You can wire up a native module and expose it to JS. Now, when you need to present FAQ screen from the native App, you invoke that native module method which forwards the event to eventHandler. The gist is below: + +```JS +// Your artifact + +// handlerId is the name of RN module loaded, eg: Enterprise +RNEventHandler.sendEvent("navigate_to_faq", handlerId); +``` + +```kt +// Your artifact + +// RNEventHandler +fun sendEvent(event: String, handlerId: String) { + EventHandlerRegistry.sendEvent(handlerId, event) +} +``` + +```kt +// Your artifact + +// EventHandlerRegistry +fun sendEvent(event: String, handlerId: String) { + eventHandlers[handlerId].invoke(event) +} +``` \ No newline at end of file diff --git a/docs/KOTLIN.md b/docs/KOTLIN.md index 2f747529..5c1425fe 100644 --- a/docs/KOTLIN.md +++ b/docs/KOTLIN.md @@ -13,8 +13,8 @@ Below is the code you need to add before you call `RNBrownfield.initialize`: ```kt import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative -loadReactNative(application); -RNBrownfield.initialize(application, packages); +loadReactNative(application) +RNBrownfield.initialize(application, packages) ``` ### API Reference From 11667bb75b0a9fe30a9af59854c9ffa186ce6859 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Mon, 21 Jul 2025 12:32:00 +0500 Subject: [PATCH 7/7] docs: update docs with a note for implementing DefaultHardwareBackBtnHandler is not required --- docs/JAVA.md | 8 ++++++++ docs/KOTLIN.md | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/JAVA.md b/docs/JAVA.md index fa1c3734..67367522 100644 --- a/docs/JAVA.md +++ b/docs/JAVA.md @@ -34,6 +34,14 @@ loadReactNative(application); RNBrownfield.initialize(application, packages); ``` +
+
+ +> Note: Previously, you were required to implement `DefaultHardwareBackBtnListener` in your calling Activity. Now with > 1.1.0 you are not required to do that step. +If you're upgrading to the latest version then you can safely remove that interface implementation from your calling Activity. + +
+ ### API Reference #### `ReactNativeBrownfield` diff --git a/docs/KOTLIN.md b/docs/KOTLIN.md index 5c1425fe..d30eec9e 100644 --- a/docs/KOTLIN.md +++ b/docs/KOTLIN.md @@ -17,6 +17,14 @@ loadReactNative(application) RNBrownfield.initialize(application, packages) ``` +
+
+ +> Note: Previously, you were required to implement `DefaultHardwareBackBtnHandler` in your calling Activity. Now with > 1.1.0 you are not required to do that step. +If you're upgrading to the latest version then you can safely remove that interface implementation from your calling Activity. + +
+ ### API Reference #### `ReactNativeBrownfield`