diff --git a/android/app/build.gradle b/android/app/build.gradle index 72fd4e2dc..ca8025128 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -10,19 +10,16 @@ def hermesEngineDir = def hermesAndroidDir = "$hermesEngineDir/android" buildscript { - ext.kotlinVersion = "1.4.20" - - def buildscriptDir = buildscript.sourceFile.getParent() - apply from: "$buildscriptDir/../test-app-util.gradle" + def androidDir = "${buildscript.sourceFile.getParent()}/../" + apply from: "$androidDir/test-app-util.gradle" + apply from: "$androidDir/dependencies.gradle" repositories { - google() jcenter() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "com.android.tools.build:gradle:4.0.2" } } @@ -45,15 +42,15 @@ apply from: file("${testAppDir}/test-app.gradle") applyTestAppModule(project, "com.microsoft.reacttestapp") project.ext.react = [ - appName: getAppName(), + appName : getAppName(), applicationId: getApplicationId(), enableFlipper: getFlipperVersion(rootDir), enableHermes : true, ] android { - compileSdkVersion 29 - buildToolsVersion "29.0.3" + compileSdkVersion sdk.version + buildToolsVersion sdk.buildToolsVersion // TODO: Remove this block when minSdkVersion >= 24. See // https://stackoverflow.com/q/53402639 for details. @@ -69,10 +66,10 @@ android { defaultConfig { applicationId project.ext.react.applicationId - minSdkVersion 21 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" + minSdkVersion sdk.minVersion + targetSdkVersion sdk.version + versionCode rntaVersion.code + versionName rntaVersion.label resValue "string", "app_name", project.ext.react.appName @@ -94,6 +91,8 @@ android { } dependencies { + implementation project(":support") + implementation "com.google.dagger:dagger:2.28.3" implementation "com.google.dagger:dagger-android:2.28.3" implementation "com.google.dagger:dagger-android-support:2.28.3" diff --git a/android/app/src/main/java/com/microsoft/reacttestapp/TestApp.kt b/android/app/src/main/java/com/microsoft/reacttestapp/TestApp.kt index 700288a2e..ff2d7f093 100644 --- a/android/app/src/main/java/com/microsoft/reacttestapp/TestApp.kt +++ b/android/app/src/main/java/com/microsoft/reacttestapp/TestApp.kt @@ -1,9 +1,11 @@ package com.microsoft.reacttestapp import android.app.Application +import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.microsoft.reacttestapp.di.DaggerTestAppComponent import com.microsoft.reacttestapp.react.TestAppReactNativeHost +import com.microsoft.reacttestapp.support.ReactTestAppLifecycleEvents import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -20,13 +22,25 @@ class TestApp : Application(), HasAndroidInjector, ReactApplication { override fun onCreate() { super.onCreate() + val eventConsumers = PackageList(this).packages + .filter { it is ReactTestAppLifecycleEvents } + .map { it as ReactTestAppLifecycleEvents } + + eventConsumers.forEach { it.onTestAppInitialized() } + val testAppComponent = DaggerTestAppComponent.builder() .binds(this) .build() - testAppComponent.inject(this) - reactNativeHostInternal.init() + reactNativeHostInternal.init( + beforeReactNativeInit = { + eventConsumers.forEach { it.onTestAppWillInitializeReactNative() } + }, + afterReactNativeInit = { + eventConsumers.forEach { it.onTestAppDidInitializeReactNative() } + }, + ) } override fun androidInjector(): AndroidInjector = dispatchingAndroidInjector diff --git a/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt b/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt index 9c548e2f2..ee3872d01 100644 --- a/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt +++ b/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt @@ -59,11 +59,24 @@ class TestAppReactNativeHost @Inject constructor( var onBundleSourceChanged: ((newSource: BundleSource) -> Unit)? = null - fun init() { + fun init(beforeReactNativeInit: () -> Unit, afterReactNativeInit: () -> Unit) { if (BuildConfig.DEBUG && hasInstance()) { error("init() can only be called once on startup") } + val reactInstanceListener = object : ReactInstanceManager.ReactInstanceEventListener { + override fun onReactContextInitialized(context: ReactContext?) { + afterReactNativeInit() + + // proactively removing the listener to avoid leaking memory + // and to avoid dupe calls to afterReactNativeInit() + reactInstanceManager.removeReactInstanceEventListener(this) + } + } + + reactInstanceManager.addReactInstanceEventListener(reactInstanceListener) + + beforeReactNativeInit() SoLoader.init(application, false) reactInstanceManager.createReactContextInBackground() diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..920e5ae97 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,13 @@ +buildscript { + repositories { + jcenter() + google() + } + + def buildscriptDir = buildscript.sourceFile.getParent() + apply from: "$buildscriptDir/dependencies.gradle" + + dependencies { + classpath "com.android.tools.build:gradle:$androidPluginVersion" + } +} diff --git a/android/dependencies.gradle b/android/dependencies.gradle new file mode 100644 index 000000000..77d94253c --- /dev/null +++ b/android/dependencies.gradle @@ -0,0 +1,15 @@ +ext { + androidPluginVersion = "4.0.2" + kotlinVersion = "1.4.20" + + rntaVersion = [ + code : 1, + label: "1.0" + ] + + sdk = [ + version : 29, + minVersion : 21, + buildToolsVersion: "29.0.3" + ] +} diff --git a/android/react-native-build.gradle b/android/react-native-build.gradle index 93a5da138..977361f37 100644 --- a/android/react-native-build.gradle +++ b/android/react-native-build.gradle @@ -1,10 +1,14 @@ buildscript { + def buildscriptDir = buildscript.sourceFile.getParent() + apply from: "$buildscriptDir/dependencies.gradle" + repositories { google() jcenter() } + dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath "com.android.tools.build:gradle:$androidPluginVersion" classpath 'de.undercouch:gradle-download-task:4.1.1' } } diff --git a/android/settings.gradle b/android/settings.gradle index dab7612d2..c52b69031 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,2 +1,2 @@ rootProject.name='react-test-app' -include ':app' +include ':app', ':support' diff --git a/android/support/build.gradle b/android/support/build.gradle new file mode 100644 index 000000000..c10a7d089 --- /dev/null +++ b/android/support/build.gradle @@ -0,0 +1,26 @@ +repositories { + jcenter() + google() +} + +apply plugin: "com.android.library" + +def androidDir = "${buildscript.sourceFile.getParent()}/../" +apply from: "$androidDir/dependencies.gradle" + +android { + compileSdkVersion sdk.version + buildToolsVersion sdk.buildToolsVersion + + defaultConfig { + minSdkVersion sdk.minVersion + targetSdkVersion sdk.version + versionCode rntaVersion.code + versionName rntaVersion.label + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} diff --git a/android/support/src/main/AndroidManifest.xml b/android/support/src/main/AndroidManifest.xml new file mode 100644 index 000000000..867262fdf --- /dev/null +++ b/android/support/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/android/support/src/main/java/com/microsoft/reacttestapp/support/ReactTestAppLifecycleEvents.java b/android/support/src/main/java/com/microsoft/reacttestapp/support/ReactTestAppLifecycleEvents.java new file mode 100644 index 000000000..cbf3d31de --- /dev/null +++ b/android/support/src/main/java/com/microsoft/reacttestapp/support/ReactTestAppLifecycleEvents.java @@ -0,0 +1,7 @@ +package com.microsoft.reacttestapp.support; + +public interface ReactTestAppLifecycleEvents { + void onTestAppInitialized(); + void onTestAppWillInitializeReactNative(); + void onTestAppDidInitializeReactNative(); +} diff --git a/example/android/build.gradle b/example/android/build.gradle index 495c5038e..1a023ff54 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1 +1,12 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + apply from: file("../node_modules/react-native-test-app/android/dependencies.gradle") + + repositories { + jcenter() + google() + } + + dependencies { + classpath "com.android.tools.build:gradle:$androidPluginVersion" + } +} diff --git a/example/ios/ExampleTests/DevSupportTests.m b/example/ios/ExampleTests/DevSupportTests.m index 57c9cdd9b..ed994439c 100644 --- a/example/ios/ExampleTests/DevSupportTests.m +++ b/example/ios/ExampleTests/DevSupportTests.m @@ -17,6 +17,8 @@ @implementation DevSupportTests - (void)testDevSupportIsLinked { XCTAssertNotNil(ReactTestAppDidInitializeNotification); + XCTAssertNotNil(ReactTestAppWillInitializeReactNativeNotification); + XCTAssertNotNil(ReactTestAppDidInitializeReactNativeNotification); XCTAssertNotNil(ReactTestAppSceneDidOpenURLNotification); } diff --git a/ios/ReactTestApp/AppDelegate.swift b/ios/ReactTestApp/AppDelegate.swift index 74e50baa4..d0cb8f220 100644 --- a/ios/ReactTestApp/AppDelegate.swift +++ b/ios/ReactTestApp/AppDelegate.swift @@ -23,6 +23,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.application = application + + defer { + NotificationCenter.default.post( + name: .ReactTestAppDidInitialize, + object: nil + ) + } + return true } diff --git a/ios/ReactTestApp/Public/ReactTestApp-DevSupport.h b/ios/ReactTestApp/Public/ReactTestApp-DevSupport.h index 54a007fbe..58633e900 100644 --- a/ios/ReactTestApp/Public/ReactTestApp-DevSupport.h +++ b/ios/ReactTestApp/Public/ReactTestApp-DevSupport.h @@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN extern NSNotificationName const ReactTestAppDidInitializeNotification; +extern NSNotificationName const ReactTestAppWillInitializeReactNativeNotification; +extern NSNotificationName const ReactTestAppDidInitializeReactNativeNotification; extern NSNotificationName const ReactTestAppSceneDidOpenURLNotification; NS_ASSUME_NONNULL_END diff --git a/ios/ReactTestApp/ReactInstance.swift b/ios/ReactTestApp/ReactInstance.swift index 7699000b7..19cbf6bf3 100644 --- a/ios/ReactTestApp/ReactInstance.swift +++ b/ios/ReactTestApp/ReactInstance.swift @@ -97,6 +97,13 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { } func initReact(onDidInitialize: @escaping () -> Void) { + if bridge == nil { + NotificationCenter.default.post( + name: .ReactTestAppWillInitializeReactNative, + object: nil + ) + } + if let bridge = bridge { if remoteBundleURL == nil { // When loading the embedded bundle, we must disable remote @@ -109,7 +116,7 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { self.bridge = bridge NotificationCenter.default.post( - name: .ReactTestAppDidInitialize, + name: .ReactTestAppDidInitializeReactNative, object: bridge ) diff --git a/ios/ReactTestApp/ReactTestApp-DevSupport.m b/ios/ReactTestApp/ReactTestApp-DevSupport.m index 31f41d0fb..f1f63d388 100644 --- a/ios/ReactTestApp/ReactTestApp-DevSupport.m +++ b/ios/ReactTestApp/ReactTestApp-DevSupport.m @@ -9,5 +9,9 @@ NSNotificationName const ReactTestAppDidInitializeNotification = @"ReactTestAppDidInitializeNotification"; +NSNotificationName const ReactTestAppWillInitializeReactNativeNotification = + @"ReactTestAppWillInitializeReactNativeNotification"; +NSNotificationName const ReactTestAppDidInitializeReactNativeNotification = + @"ReactTestAppDidInitializeReactNativeNotification"; NSNotificationName const ReactTestAppSceneDidOpenURLNotification = @"ReactTestAppSceneDidOpenURLNotification"; diff --git a/plopfile.js b/plopfile.js index b96d9ffbe..a7d5b8a20 100644 --- a/plopfile.js +++ b/plopfile.js @@ -299,6 +299,11 @@ module.exports = (/** @type {import("plop").NodePlopAPI} */ plop) => { path: `${prefix}gradlew.bat`, templateFile: path.join(androidTemplateDir, "gradlew.bat"), }); + actions.push({ + type: "add", + path: `${prefix}build.gradle`, + templateFile: path.join(androidTemplateDir, "build.gradle"), + }); actions.push({ type: "add", path: `${prefix}settings.gradle`, diff --git a/test-app.gradle b/test-app.gradle index 1d86c7d04..a790b4f2c 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -17,8 +17,12 @@ private static void apply(Settings settings) { } settings.include(":app") + settings.include(":support") + settings.project(":app") .projectDir = new File("${projectDir}/android/app") + settings.project(":support") + .projectDir = new File("${projectDir}/android/support") } def scriptDir = buildscript.sourceFile.getParent()