From b148843cc2fc01dc9f03479544dc4117bbbf19b2 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 30 Mar 2025 20:17:09 +0500 Subject: [PATCH 01/10] feat: use the relevant view specific to arch for presenting RN --- .../java/com/helloworldreact/RNViewFactory.kt | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/RNViewFactory.kt b/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/RNViewFactory.kt index 5b077c744..2d27c36b2 100644 --- a/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/RNViewFactory.kt +++ b/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/RNViewFactory.kt @@ -3,22 +3,55 @@ package com.helloworldreact import android.content.Context import android.os.Bundle import android.widget.FrameLayout +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.facebook.react.ReactDelegate import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactRootView object RNViewFactory { fun createFrameLayout( context: Context, - params: Bundle? = null, + activity: FragmentActivity, + initialParams: Bundle? = null, ): FrameLayout { + val componentName = "HelloWorld" + val reactHost = ReactNativeHostManager.shared.getReactHost() + + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + val reactDelegate = ReactDelegate(activity, reactHost!!, componentName, initialParams) + val lifecycleObserver = getLifeCycleObserver(reactDelegate) + + activity.lifecycle.addObserver(lifecycleObserver) + reactDelegate.loadApp() + return reactDelegate.reactRootView as FrameLayout + } + + val instanceManager: ReactInstanceManager? = ReactNativeHostManager.shared.getReactNativeHost()?.reactInstanceManager val reactView = ReactRootView(context) - val reactNativeHost = ReactNativeHostManager.shared.getReactNativeHost() - val instanceManager: ReactInstanceManager? = reactNativeHost?.reactInstanceManager reactView.startReactApplication( instanceManager, - "HelloWorld", - params, + componentName, + initialParams, ) return reactView } + + private fun getLifeCycleObserver(reactDelegate: ReactDelegate): DefaultLifecycleObserver { + return object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + reactDelegate.onHostResume() + } + + override fun onPause(owner: LifecycleOwner) { + reactDelegate.onHostPause() + } + + override fun onDestroy(owner: LifecycleOwner) { + reactDelegate.onHostDestroy() + owner.lifecycle.removeObserver(this) + } + } + } } \ No newline at end of file From a8cdf9d0f2f841b1e10c52b5a06f710ec42077bb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 30 Mar 2025 20:17:31 +0500 Subject: [PATCH 02/10] feat: load files for new arch --- .../helloworldreact/ReactNativeHostManager.kt | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/ReactNativeHostManager.kt b/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/ReactNativeHostManager.kt index 12af30361..875db9dcf 100644 --- a/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/ReactNativeHostManager.kt +++ b/packages/plugin-brownfield-android/template/android/helloworldreact/src/main/java/com/helloworldreact/ReactNativeHostManager.kt @@ -4,6 +4,10 @@ import android.app.Application import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.react.soloader.OpenSourceMergedSoMapping import com.facebook.soloader.SoLoader @@ -12,39 +16,49 @@ class ReactNativeHostManager { companion object { val shared: ReactNativeHostManager by lazy { ReactNativeHostManager() } private var reactNativeHost: ReactNativeHost? = null + private var reactHost: ReactHost? = null } fun getReactNativeHost(): ReactNativeHost? { return reactNativeHost } - fun initialize( - application: Application, - ) { - if (reactNativeHost == null) { - SoLoader.init(application, OpenSourceMergedSoMapping) - reactNativeHost = - object : DefaultReactNativeHost(application) { - override fun getUseDeveloperSupport(): Boolean { - return BuildConfig.DEBUG - } + fun getReactHost(): ReactHost? { + return reactHost + } + + fun initialize(application: Application) { + if (reactNativeHost != null && reactHost != null) { + return + } + + SoLoader.init(application, 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() + } + val reactApp = object : ReactApplication { + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(application) { override fun getPackages(): MutableList { - val packages: MutableList = PackageList(application).packages - return packages + return PackageList(application).packages } - override fun getJSMainModuleName(): String { - return "index" - } + override fun getJSMainModuleName(): String = "index" + override fun getBundleAssetName(): String = "index.android.bundle" - override fun getBundleAssetName(): String { - return "index.android.bundle" - } + override fun getUseDeveloperSupport() = BuildConfig.DEBUG override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } + + override val reactHost: ReactHost + get() = getDefaultReactHost(application, reactNativeHost) } + + reactNativeHost = reactApp.reactNativeHost + reactHost = reactApp.reactHost } } From a04d7988ae251cae0ca0687983bf4e3c6a825331 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 30 Mar 2025 20:17:58 +0500 Subject: [PATCH 03/10] feat: copy codegen libs for both variants --- .../android/helloworldreact/build.gradle.kts | 100 +++++++++++++----- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts index 4dd81f99f..8d1a7fef7 100644 --- a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts +++ b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts @@ -1,8 +1,16 @@ +import groovy.json.JsonOutput +import groovy.json.JsonSlurper + plugins { id("com.android.library") id("org.jetbrains.kotlin.android") id("com.callstack.react.brownfield") `maven-publish` + id("com.facebook.react") +} + +react { + autolinkLibrariesWithApp() } repositories { @@ -20,6 +28,9 @@ android { defaultConfig { minSdk = 24 + + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", properties["newArchEnabled"].toString()) + buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString()) } compileOptions { @@ -31,19 +42,23 @@ android { jvmTarget = "17" } - buildTypes { - release { - buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", properties["newArchEnabled"].toString()) - buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString()) - } - } - sourceSets { getByName("main") { assets.srcDirs("$appBuildDir/generated/assets/createBundleReleaseJsAndAssets") res.srcDirs("$appBuildDir/generated/res/createBundleReleaseJsAndAssets") java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") } + getByName("release") { + jniLibs.srcDirs("libsRelease") + } + getByName("debug") { + jniLibs.srcDirs("libsDebug") + } + } + publishing { + multipleVariants { + allVariants() + } } } @@ -53,20 +68,19 @@ publishing { groupId = "com.helloworldreact" artifactId = "helloworld" version = "0.0.1-local" - artifact("$moduleBuildDir/outputs/aar/helloworldreact-release.aar") + afterEvaluate { + from(components.getByName("default")) + } pom { withXml { - asNode().appendNode("dependencies").apply { - configurations.getByName("api").allDependencies.forEach { dependency -> - appendNode("dependency").apply { - appendNode("groupId", dependency.group) - appendNode("artifactId", dependency.name) - appendNode("version", dependency.version) - appendNode("scope", "compile") - } - } - } + //removing RN dependencies like e.g. react-native-svg + //added by "from(components.getByName("default"))" to pom file + val dependenciesNode = (asNode().get("dependencies") as groovy.util.NodeList).first() as groovy.util.Node + dependenciesNode.children() + .filterIsInstance() + .filter { (it.get("groupId") as groovy.util.NodeList).text() == rootProject.name } + .forEach { dependenciesNode.remove(it) } } } } @@ -78,8 +92,8 @@ publishing { } dependencies { - api("com.facebook.react:react-android:0.77.0-rc.2") - api("com.facebook.react:hermes-android:0.77.0-rc.2") + api("com.facebook.react:react-android:0.78.0") + api("com.facebook.react:hermes-android:0.78.0") } tasks.register("copyAutolinkingSources") { @@ -88,13 +102,45 @@ tasks.register("copyAutolinkingSources") { into("$moduleBuildDir/$autolinkingJavaSources") } -tasks.named("preBuild").configure{ - dependsOn("copyAutolinkingSources") - val buildType = when { - gradle.startParameter.taskNames.any { it.contains("Release", ignoreCase = true) } -> "Release" - else -> "Debug" +androidComponents { + onVariants { variant -> + val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } + + tasks.register("copy${buildType}LibSources") { + dependsOn(":app:generateCodegenSchemaFromJavaScript") + dependsOn(":app:strip${buildType}DebugSymbols") + + dependsOn(":helloworldreact:generateCodegenSchemaFromJavaScript") + from("${appBuildDir}/intermediates/stripped_native_libs/${buildType?.lowercase()}/strip${buildType}DebugSymbols/out/lib") + into("${rootProject.projectDir}/helloworldreact/libs${buildType}") + + include("**/libappmodules.so", "**/libreact_codegen_*.so") + } + + tasks.named("preBuild").configure { + dependsOn("copyAutolinkingSources") + dependsOn("copy${buildType}LibSources") + if (buildType == "Release") { + dependsOn(":app:createBundleReleaseJsAndAssets") + } + } } - if (buildType == "Release") { - dependsOn(":app:createBundleReleaseJsAndAssets") +} + +//removing RN dependencies like e.g. react-native-svg +//added by "from(components.getByName("default"))" to "module" file +tasks.register("removeDependenciesFromModuleFile") { + doLast { + file("$moduleBuildDir/publications/mavenAar/module.json").run { + val json = inputStream().use { JsonSlurper().parse(it) as Map } + (json["variants"] as? List>)?.forEach { variant -> + (variant["dependencies"] as? MutableList>)?.removeAll { it["group"] == rootProject.name } + } + writer().use { it.write(JsonOutput.prettyPrint(JsonOutput.toJson(json))) } + } } } + +tasks.named("generateMetadataFileForMavenAarPublication") { + finalizedBy("removeDependenciesFromModuleFile") +} \ No newline at end of file From 5e4233f7ca39189739145b7966186742aa99ee43 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 30 Mar 2025 20:18:07 +0500 Subject: [PATCH 04/10] docs: update docs --- website/docs/docs/brownfield/android.md | 207 ++++++++++++++++++------ 1 file changed, 157 insertions(+), 50 deletions(-) diff --git a/website/docs/docs/brownfield/android.md b/website/docs/docs/brownfield/android.md index a8c0a5601..1ec31b877 100644 --- a/website/docs/docs/brownfield/android.md +++ b/website/docs/docs/brownfield/android.md @@ -14,9 +14,9 @@ First, we'll create a new Android library module in your React Native project. T 1. After the sync completes, run your React Native app to make sure everything works 1. Test the build by running `./gradlew assembleRelease` in the android directory -## 2. Set Up the Fat AAR Gradle Plugin +## 2. Set Up the AAR Gradle Plugin -We need a special Gradle plugin to create a "fat" AAR that includes all dependencies. We'll use the `brownfield-gradle-plugin` plugin from Callstack. +We need a special Gradle plugin to create an AAR that includes all dependencies. We'll use the `brownfield-gradle-plugin` plugin from Callstack. 1. Add the gradle plugin dependency to your `android/build.gradle`: @@ -67,6 +67,10 @@ import android.app.Application import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.react.soloader.OpenSourceMergedSoMapping import com.facebook.soloader.SoLoader @@ -75,36 +79,50 @@ class ReactNativeHostManager { companion object { val shared: ReactNativeHostManager by lazy { ReactNativeHostManager() } private var reactNativeHost: ReactNativeHost? = null + private var reactHost: ReactHost? = null } fun getReactNativeHost(): ReactNativeHost? { return reactNativeHost } + fun getReactHost(): ReactHost? { + return reactHost + } + fun initialize(application: Application) { - if (reactNativeHost == null) { - SoLoader.init(application, OpenSourceMergedSoMapping) - reactNativeHost = object : DefaultReactNativeHost(application) { - override fun getUseDeveloperSupport(): Boolean { - return BuildConfig.DEBUG - } + if (reactNativeHost != null && reactHost != null) { + return + } - override fun getPackages(): MutableList { - return PackageList(application).packages - } + SoLoader.init(application, 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() + } - override fun getJSMainModuleName(): String { - return "index" - } + val reactApp = object : ReactApplication { + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(application) { + override fun getPackages(): MutableList { + return PackageList(application).packages + } - override fun getBundleAssetName(): String { - return "index.android.bundle" + override fun getJSMainModuleName(): String = "index" + override fun getBundleAssetName(): String = "index.android.bundle" + + override fun getUseDeveloperSupport() = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } + override val reactHost: ReactHost + get() = getDefaultReactHost(application, reactNativeHost) } + + reactNativeHost = reactApp.reactNativeHost + reactHost = reactApp.reactHost } } ``` @@ -127,11 +145,11 @@ Add build configuration fields: ```gradle title="rnbrownfield/build.gradle.kts" {4-5} android { - buildTypes { - release { - buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", properties["newArchEnabled"].toString()) - buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString()) - } + defaultConfig { + minSdk = 24 + + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", properties["newArchEnabled"].toString()) + buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString()) } } ``` @@ -158,8 +176,12 @@ tasks.register("copyAutolinkingSources") { into("$moduleBuildDir/$autolinkingJavaSources") } -tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") +androidComponents { + onVariants { variant -> + tasks.named("preBuild").configure { + dependsOn("copyAutolinkingSources") + } + } } ``` @@ -177,14 +199,16 @@ android { } } -tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") - val buildType = when { - gradle.startParameter.taskNames.any { it.contains("Release", ignoreCase = true) } -> "Release" - else -> "Debug" - } - if (buildType == "Release") { - dependsOn(":app:createBundleReleaseJsAndAssets") +androidComponents { + onVariants { variant -> + val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } + + tasks.named("preBuild").configure { + dependsOn("copyAutolinkingSources") + if (buildType == "Release") { + dependsOn(":app:createBundleReleaseJsAndAssets") + } + } } } ``` @@ -197,24 +221,108 @@ Create a new file called `RNViewFactory.kt` to wrap your React Native UI in a `F import android.content.Context import android.os.Bundle import android.widget.FrameLayout +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.facebook.react.ReactDelegate import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactRootView object RNViewFactory { fun createFrameLayout( context: Context, - params: Bundle? = null, + activity: FragmentActivity, + initialParams: Bundle? = null, ): FrameLayout { + val componentName = "BrownFieldTest" + val reactHost = ReactNativeHostManager.shared.getReactHost() + + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + val reactDelegate = ReactDelegate(activity, reactHost!!, componentName, initialParams) + val lifecycleObserver = getLifeCycleObserver(reactDelegate) + + activity.lifecycle.addObserver(lifecycleObserver) + reactDelegate.loadApp() + return reactDelegate.reactRootView as FrameLayout + } + + val instanceManager: ReactInstanceManager? = ReactNativeHostManager.shared.getReactNativeHost()?.reactInstanceManager val reactView = ReactRootView(context) - val reactNativeHost = ReactNativeHostManager.shared.getReactNativeHost() - val instanceManager: ReactInstanceManager? = reactNativeHost?.reactInstanceManager reactView.startReactApplication( instanceManager, - "BrownfieldTest", - params, + componentName, + initialParams, ) return reactView } + + private fun getLifeCycleObserver(reactDelegate: ReactDelegate): DefaultLifecycleObserver { + return object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + reactDelegate.onHostResume() + } + + override fun onPause(owner: LifecycleOwner) { + reactDelegate.onHostPause() + } + + override fun onDestroy(owner: LifecycleOwner) { + reactDelegate.onHostDestroy() + owner.lifecycle.removeObserver(this) + } + } + } +} +``` + +## 7. Copy JNI libs + +Add the following to your `rnbrownfield/build.gradle.kts`: + +```gradle +android { + sourceSets { + getByName("main") { + assets.srcDirs("$appBuildDir/generated/assets/createBundleReleaseJsAndAssets") + java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") + } + getByName("release") { + jniLibs.srcDirs("libsRelease") + } + getByName("debug") { + jniLibs.srcDirs("libsDebug") + } + } + publishing { + multipleVariants { + allVariants() + } + } +} + +androidComponents { + onVariants { variant -> + val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } + + tasks.register("copy${buildType}LibSources") { + dependsOn(":app:generateCodegenSchemaFromJavaScript") + dependsOn(":app:strip${buildType}DebugSymbols") + + dependsOn(":rnbrownfield:generateCodegenSchemaFromJavaScript") + from("${appBuildDir}/intermediates/stripped_native_libs/${buildType?.lowercase()}/strip${buildType}DebugSymbols/out/lib") + into("${rootProject.projectDir}/rnbrownfield/libs${buildType}") + + include("**/libappmodules.so", "**/libreact_codegen_*.so") + } + + tasks.named("preBuild").configure { + dependsOn("copyAutolinkingSources") + dependsOn("copy${buildType}LibSources") + if (buildType == "Release") { + dependsOn(":app:createBundleReleaseJsAndAssets") + } + } + } } ``` @@ -240,20 +348,19 @@ publishing { groupId = "com.callstack" artifactId = "rnbrownfield" version = "0.0.1-local" - artifact("$moduleBuildDir/outputs/aar/rnbrownfield-release.aar") + afterEvaluate { + from(components.getByName("default")) + } pom { withXml { - asNode().appendNode("dependencies").apply { - configurations.getByName("api").allDependencies.forEach { dependency -> - appendNode("dependency").apply { - appendNode("groupId", dependency.group) - appendNode("artifactId", dependency.name) - appendNode("version", dependency.version) - appendNode("scope", "compile") - } - } - } + //removing RN dependencies like e.g. react-native-svg + //added by "from(components.getByName("default"))" to pom file + val dependenciesNode = (asNode().get("dependencies") as groovy.util.NodeList).first() as groovy.util.Node + dependenciesNode.children() + .filterIsInstance() + .filter { (it.get("groupId") as groovy.util.NodeList).text() == rootProject.name } + .forEach { dependenciesNode.remove(it) } } } } From aa35d110255f641a0144f9b2624b283782c3657d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 3 Apr 2025 21:51:46 +0500 Subject: [PATCH 05/10] docs: update docs --- website/docs/docs/brownfield/android.md | 6 ++++-- .../docs/docs/brownfield/assets}/create_module.png | Bin .../brownfield/assets}/verify_aar_plugin_setup.png | Bin 3 files changed, 4 insertions(+), 2 deletions(-) rename {docs/assets/android-brownfield => website/docs/docs/brownfield/assets}/create_module.png (100%) rename {docs/assets/android-brownfield => website/docs/docs/brownfield/assets}/verify_aar_plugin_setup.png (100%) diff --git a/website/docs/docs/brownfield/android.md b/website/docs/docs/brownfield/android.md index 1ec31b877..f2f653085 100644 --- a/website/docs/docs/brownfield/android.md +++ b/website/docs/docs/brownfield/android.md @@ -10,7 +10,7 @@ First, we'll create a new Android library module in your React Native project. T 1. Open your React Native project's `android` folder in Android Studio 1. Go to File → New Module → Android Library - ![Create new module in Android Studio](/create_module.png) + ![Create new module in Android Studio](./assets/create_module.png) 1. After the sync completes, run your React Native app to make sure everything works 1. Test the build by running `./gradlew assembleRelease` in the android directory @@ -43,7 +43,7 @@ We need a special Gradle plugin to create an AAR that includes all dependencies. ``` 1. Run `./gradlew assembleRelease` to verify the setup - ![Verify AAR plugin setup](/verify_aar_plugin_setup.png) + ![Verify AAR plugin setup](./assets/verify_aar_plugin_setup.png) ## 3. Add React Native Dependencies @@ -326,6 +326,8 @@ androidComponents { } ``` +>Note: Add `**/*.so` to your .gitignore file, as to not commit these .so files. The reason is they are auto-generated each time. + ## 7. Configure Maven Publishing Add the Maven publish plugin to your `rnbrownfield/build.gradle.kts`: diff --git a/docs/assets/android-brownfield/create_module.png b/website/docs/docs/brownfield/assets/create_module.png similarity index 100% rename from docs/assets/android-brownfield/create_module.png rename to website/docs/docs/brownfield/assets/create_module.png diff --git a/docs/assets/android-brownfield/verify_aar_plugin_setup.png b/website/docs/docs/brownfield/assets/verify_aar_plugin_setup.png similarity index 100% rename from docs/assets/android-brownfield/verify_aar_plugin_setup.png rename to website/docs/docs/brownfield/assets/verify_aar_plugin_setup.png From efba068553659995e1a2c2fe0a3a3fa4148d3721 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 6 Apr 2025 21:11:45 +0500 Subject: [PATCH 06/10] refactor: remove unused code --- .../template/android/build.gradle | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/build.gradle b/packages/plugin-brownfield-android/template/android/build.gradle index 8ac7f82eb..06bd44f7b 100644 --- a/packages/plugin-brownfield-android/template/android/build.gradle +++ b/packages/plugin-brownfield-android/template/android/build.gradle @@ -9,14 +9,6 @@ buildscript { } repositories { google() - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/callstack/big-fat-aar") - credentials { - username = System.getenv("GITHUB_USERNAME") - password = System.getenv("GITHUB_TOKEN") - } - } mavenCentral() } dependencies { From 243ec933f5bb638bb0758ab6c988e75b70cbb353 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 6 Apr 2025 21:12:21 +0500 Subject: [PATCH 07/10] refactor: remove code as it is moved to react-native-brownfield --- .../android/helloworldreact/build.gradle.kts | 61 +++---------------- 1 file changed, 10 insertions(+), 51 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts index 8d1a7fef7..c92aaf84d 100644 --- a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts +++ b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts @@ -17,10 +17,7 @@ repositories { mavenCentral() } -val appProject = project(":app") -val appBuildDir: Directory = appProject.layout.buildDirectory.get() val moduleBuildDir: Directory = layout.buildDirectory.get() -val autolinkingJavaSources = "generated/autolinking/src/main/java" android { namespace = "com.helloworldreact" @@ -42,19 +39,6 @@ android { jvmTarget = "17" } - sourceSets { - getByName("main") { - assets.srcDirs("$appBuildDir/generated/assets/createBundleReleaseJsAndAssets") - res.srcDirs("$appBuildDir/generated/res/createBundleReleaseJsAndAssets") - java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") - } - getByName("release") { - jniLibs.srcDirs("libsRelease") - } - getByName("debug") { - jniLibs.srcDirs("libsDebug") - } - } publishing { multipleVariants { allVariants() @@ -74,8 +58,11 @@ publishing { pom { withXml { - //removing RN dependencies like e.g. react-native-svg - //added by "from(components.getByName("default"))" to pom file + /** + * As a result of `from(components.getByName("default")` all of the project + * dependencies are added to `pom.xml` file. We do not need the react-native + * third party dependencies to be a part of it as we embed those dependencies. + */ val dependenciesNode = (asNode().get("dependencies") as groovy.util.NodeList).first() as groovy.util.Node dependenciesNode.children() .filterIsInstance() @@ -96,39 +83,11 @@ dependencies { api("com.facebook.react:hermes-android:0.78.0") } -tasks.register("copyAutolinkingSources") { - dependsOn(":app:generateAutolinkingPackageList") - from("$appBuildDir/$autolinkingJavaSources") - into("$moduleBuildDir/$autolinkingJavaSources") -} - -androidComponents { - onVariants { variant -> - val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } - - tasks.register("copy${buildType}LibSources") { - dependsOn(":app:generateCodegenSchemaFromJavaScript") - dependsOn(":app:strip${buildType}DebugSymbols") - - dependsOn(":helloworldreact:generateCodegenSchemaFromJavaScript") - from("${appBuildDir}/intermediates/stripped_native_libs/${buildType?.lowercase()}/strip${buildType}DebugSymbols/out/lib") - into("${rootProject.projectDir}/helloworldreact/libs${buildType}") - - include("**/libappmodules.so", "**/libreact_codegen_*.so") - } - - tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") - dependsOn("copy${buildType}LibSources") - if (buildType == "Release") { - dependsOn(":app:createBundleReleaseJsAndAssets") - } - } - } -} - -//removing RN dependencies like e.g. react-native-svg -//added by "from(components.getByName("default"))" to "module" file +/** + * As a result of `from(components.getByName("default")` all of the project + * dependencies are added to `module.json` file. We do not need the react-native + * third party dependencies to be a part of it as we embed those dependencies. + */ tasks.register("removeDependenciesFromModuleFile") { doLast { file("$moduleBuildDir/publications/mavenAar/module.json").run { From c854a7794884f8e7d9b473156467f1b52cb9ea62 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Sun, 6 Apr 2025 21:31:19 +0500 Subject: [PATCH 08/10] docs: update android.md --- .../android/helloworldreact/build.gradle.kts | 4 +- website/docs/docs/brownfield/android.md | 155 ++++-------------- 2 files changed, 38 insertions(+), 121 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts index c92aaf84d..2e3e83190 100644 --- a/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts +++ b/packages/plugin-brownfield-android/template/android/helloworldreact/build.gradle.kts @@ -17,8 +17,6 @@ repositories { mavenCentral() } -val moduleBuildDir: Directory = layout.buildDirectory.get() - android { namespace = "com.helloworldreact" compileSdk = 34 @@ -83,6 +81,8 @@ dependencies { api("com.facebook.react:hermes-android:0.78.0") } +val moduleBuildDir: Directory = layout.buildDirectory.get() + /** * As a result of `from(components.getByName("default")` all of the project * dependencies are added to `module.json` file. We do not need the react-native diff --git a/website/docs/docs/brownfield/android.md b/website/docs/docs/brownfield/android.md index f2f653085..bcd61f1ea 100644 --- a/website/docs/docs/brownfield/android.md +++ b/website/docs/docs/brownfield/android.md @@ -43,6 +43,7 @@ We need a special Gradle plugin to create an AAR that includes all dependencies. ``` 1. Run `./gradlew assembleRelease` to verify the setup + ![Verify AAR plugin setup](./assets/verify_aar_plugin_setup.png) ## 3. Add React Native Dependencies @@ -154,66 +155,7 @@ android { } ``` -Set up autolinking: - -```gradle title="rnbrownfield/build.gradle.kts" {1-4,7-11,14-22} -val appProject = project(":app") -val appBuildDir: Directory = appProject.layout.buildDirectory.get() -val moduleBuildDir: Directory = layout.buildDirectory.get() -val autolinkingJavaSources = "generated/autolinking/src/main/java" - -android { - sourceSets { - getByName("main") { - java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") - } - } -} - -tasks.register("copyAutolinkingSources") { - dependsOn(":app:generateAutolinkingPackageList") - from("$appBuildDir/$autolinkingJavaSources") - into("$moduleBuildDir/$autolinkingJavaSources") -} - -androidComponents { - onVariants { variant -> - tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") - } - } -} -``` - -## 5. Include the JavaScript Bundle - -Update your `rnbrownfield/build.gradle.kts` to include the JS bundle in the AAR: - -```gradle title="rnbrownfield/build.gradle.kts" {4,12-18} -android { - sourceSets { - getByName("main") { - assets.srcDirs("$appBuildDir/generated/assets/createBundleReleaseJsAndAssets") - java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") - } - } -} - -androidComponents { - onVariants { variant -> - val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } - - tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") - if (buildType == "Release") { - dependsOn(":app:createBundleReleaseJsAndAssets") - } - } - } -} -``` - -## 6. Create the React Native Entry Point +## 5. Create the React Native Entry Point Create a new file called `RNViewFactory.kt` to wrap your React Native UI in a `FrameLayout`: @@ -275,60 +217,7 @@ object RNViewFactory { } ``` -## 7. Copy JNI libs - -Add the following to your `rnbrownfield/build.gradle.kts`: - -```gradle -android { - sourceSets { - getByName("main") { - assets.srcDirs("$appBuildDir/generated/assets/createBundleReleaseJsAndAssets") - java.srcDirs("$moduleBuildDir/$autolinkingJavaSources") - } - getByName("release") { - jniLibs.srcDirs("libsRelease") - } - getByName("debug") { - jniLibs.srcDirs("libsDebug") - } - } - publishing { - multipleVariants { - allVariants() - } - } -} - -androidComponents { - onVariants { variant -> - val buildType = variant.buildType?.replaceFirstChar { it.titlecase() } - - tasks.register("copy${buildType}LibSources") { - dependsOn(":app:generateCodegenSchemaFromJavaScript") - dependsOn(":app:strip${buildType}DebugSymbols") - - dependsOn(":rnbrownfield:generateCodegenSchemaFromJavaScript") - from("${appBuildDir}/intermediates/stripped_native_libs/${buildType?.lowercase()}/strip${buildType}DebugSymbols/out/lib") - into("${rootProject.projectDir}/rnbrownfield/libs${buildType}") - - include("**/libappmodules.so", "**/libreact_codegen_*.so") - } - - tasks.named("preBuild").configure { - dependsOn("copyAutolinkingSources") - dependsOn("copy${buildType}LibSources") - if (buildType == "Release") { - dependsOn(":app:createBundleReleaseJsAndAssets") - } - } - } -} -``` - ->Note: Add `**/*.so` to your .gitignore file, as to not commit these .so files. The reason is they are auto-generated each time. - -## 7. Configure Maven Publishing +## 6. Configure Maven Publishing Add the Maven publish plugin to your `rnbrownfield/build.gradle.kts`: @@ -356,8 +245,11 @@ publishing { pom { withXml { - //removing RN dependencies like e.g. react-native-svg - //added by "from(components.getByName("default"))" to pom file + /** + * As a result of `from(components.getByName("default")` all of the project + * dependencies are added to `pom.xml` file. We do not need the react-native + * third party dependencies to be a part of it as we embed those dependencies. + */ val dependenciesNode = (asNode().get("dependencies") as groovy.util.NodeList).first() as groovy.util.Node dependenciesNode.children() .filterIsInstance() @@ -372,9 +264,32 @@ publishing { mavenLocal() // Publishes to the local Maven repository (~/.m2/repository by default) } } + +val moduleBuildDir: Directory = layout.buildDirectory.get() + +/** + * As a result of `from(components.getByName("default")` all of the project + * dependencies are added to `module.json` file. We do not need the react-native + * third party dependencies to be a part of it as we embed those dependencies. + */ +tasks.register("removeDependenciesFromModuleFile") { + doLast { + file("$moduleBuildDir/publications/mavenAar/module.json").run { + val json = inputStream().use { JsonSlurper().parse(it) as Map } + (json["variants"] as? List>)?.forEach { variant -> + (variant["dependencies"] as? MutableList>)?.removeAll { it["group"] == rootProject.name } + } + writer().use { it.write(JsonOutput.prettyPrint(JsonOutput.toJson(json))) } + } + } +} + +tasks.named("generateMetadataFileForMavenAarPublication") { + finalizedBy("removeDependenciesFromModuleFile") +} ``` -## 8. Set up RNEF for AAR generation +## 7. Set up RNEF for AAR generation 1. Add `@rnef/plugin-brownfield-android` to your dependencies 1. Update your `rnef.config.mjs`: @@ -399,7 +314,7 @@ publishing { rnef publish-local:aar --module-name rnbrownfield ``` -## 9. Add the AAR to Your Android App +## 8. Add the AAR to Your Android App > Note: You'll need an existing Android app or create a new one in Android Studio. @@ -423,7 +338,7 @@ publishing { } ``` -## 10. Show the React Native UI +## 9. Show the React Native UI Create a new `RNAppFragment.kt`: @@ -492,3 +407,5 @@ class MainActivity : AppCompatActivity() { ``` Now you can run your app and test the React Native integration! + +>Note: `brownfield-gradle-plugin` copies `.so` files to the `lib` folder. Make sure to add `**/*.so` to your .gitignore file, as to not commit these .so files. The reason is they are auto-generated each time. \ No newline at end of file From 13c5baa7ff817ae1a90debb1ff14a8c660488cf2 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 10 Apr 2025 17:51:50 +0500 Subject: [PATCH 09/10] chore: bump brownfield-gradle-plugin --- .../plugin-brownfield-android/template/android/build.gradle | 2 +- website/docs/docs/brownfield/android.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-brownfield-android/template/android/build.gradle b/packages/plugin-brownfield-android/template/android/build.gradle index 06bd44f7b..0c8957dc8 100644 --- a/packages/plugin-brownfield-android/template/android/build.gradle +++ b/packages/plugin-brownfield-android/template/android/build.gradle @@ -15,7 +15,7 @@ buildscript { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - classpath("com.callstack.react:brownfield-gradle-plugin:0.2.0") + classpath("com.callstack.react:brownfield-gradle-plugin:0.3.0") } } diff --git a/website/docs/docs/brownfield/android.md b/website/docs/docs/brownfield/android.md index bcd61f1ea..d6c042f62 100644 --- a/website/docs/docs/brownfield/android.md +++ b/website/docs/docs/brownfield/android.md @@ -27,7 +27,7 @@ We need a special Gradle plugin to create an AAR that includes all dependencies. mavenCentral() } dependencies { - classpath("com.callstack.react:brownfield-gradle-plugin:0.2.0") + classpath("com.callstack.react:brownfield-gradle-plugin:0.3.0") } } ``` From 71cfdd8e612ad09fd7f969d3a1ea0be72d0dfedc Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 10 Apr 2025 17:55:15 +0500 Subject: [PATCH 10/10] chore: generate changeset --- .changeset/sharp-berries-relax.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/sharp-berries-relax.md diff --git a/.changeset/sharp-berries-relax.md b/.changeset/sharp-berries-relax.md new file mode 100644 index 000000000..5952d7f13 --- /dev/null +++ b/.changeset/sharp-berries-relax.md @@ -0,0 +1,6 @@ +--- +'@rnef/plugin-brownfield-android': minor +'rnef-docs': patch +--- + +correctly load and present RNViews for the respective RN arch