From c8ad23ae1a37078ed385bc86a122b9a7352fddba Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Thu, 2 Nov 2023 16:23:15 +0000 Subject: [PATCH] [0.73] Collection of Android picks for RC4 (#41221) Co-authored-by: Lulu Wu Co-authored-by: Gabriel Donadel fix `java.lang.NoSuchMethodError` for Bridgeless (#41081) resolved: https://github.com/facebook/react-native/pull/41081 resolved: https://github.com/facebook/react-native/pull/40999 resolved: https://github.com/facebook/react-native/pull/41165 Fix RNTester not showing Redbox when Metro is not connected (#41191) resolved: https://github.com/facebook/react-native/pull/41191 resolved: https://github.com/facebook/react-native/pull/41190 resolved: https://github.com/facebook/react-native/pull/41085 resolved: https://github.com/facebook/react-native/pull/41206 --- .../kotlin/com/facebook/react/ReactPlugin.kt | 2 + .../react/utils/AgpConfiguratorUtils.kt | 42 ++++++++++++ .../react/utils/AgpConfiguratorUtilsTest.kt | 65 +++++++++++++++++++ .../devsupport/BridgeDevSupportManager.java | 11 ---- .../devsupport/DevSupportManagerBase.java | 6 +- .../modules/devloading/DevLoadingModule.java | 12 +--- .../react/runtime/JSTimerExecutor.java | 5 +- .../facebook/react/runtime/ReactHostImpl.java | 5 ++ .../facebook/react/runtime/ReactInstance.java | 45 +++++++++++-- .../react/uimanager/UIManagerHelper.java | 19 +++--- .../react-native/gradle/libs.versions.toml | 2 +- 11 files changed, 177 insertions(+), 37 deletions(-) create mode 100644 packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/AgpConfiguratorUtilsTest.kt diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 6889f4c4448689..718dc4e4970cbe 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -15,6 +15,7 @@ import com.facebook.react.tasks.GenerateCodegenSchemaTask import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts +import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap import com.facebook.react.utils.DependencyUtils.configureDependencies import com.facebook.react.utils.DependencyUtils.configureRepositories @@ -80,6 +81,7 @@ class ReactPlugin : Plugin { // Library Only Configuration configureBuildConfigFieldsForLibraries(project) + configureNamespaceForLibraries(project) project.pluginManager.withPlugin("com.android.library") { configureCodegen(project, extension, rootExtension, isLibrary = true) } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt index 4f19af3ff1d1a7..90593bdbf7419c 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt @@ -8,12 +8,17 @@ package com.facebook.react.utils import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.gradle.LibraryExtension import com.facebook.react.ReactExtension import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled +import java.io.File +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.plugins.AppliedPlugin +import org.w3c.dom.Element @Suppress("UnstableApiUsage") internal object AgpConfiguratorUtils { @@ -63,6 +68,43 @@ internal object AgpConfiguratorUtils { project.pluginManager.withPlugin("com.android.application", action) project.pluginManager.withPlugin("com.android.library", action) } + + fun configureNamespaceForLibraries(appProject: Project) { + appProject.rootProject.allprojects { subproject -> + subproject.pluginManager.withPlugin("com.android.library") { + subproject.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext -> + if (ext.namespace == null) { + val android = subproject.extensions.getByType(LibraryExtension::class.java) + val manifestFile = android.sourceSets.getByName("main").manifest.srcFile + + manifestFile + .takeIf { it.exists() } + ?.let { file -> + getPackageNameFromManifest(file)?.let { packageName -> + ext.namespace = packageName + } + } + } + } + } + } + } } const val DEFAULT_DEV_SERVER_PORT = "8081" + +fun getPackageNameFromManifest(manifest: File): String? { + val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + val builder: DocumentBuilder = factory.newDocumentBuilder() + + try { + val xmlDocument = builder.parse(manifest) + + val manifestElement = xmlDocument.getElementsByTagName("manifest").item(0) as? Element + val packageName = manifestElement?.getAttribute("package") + + return if (packageName.isNullOrEmpty()) null else packageName + } catch (e: Exception) { + return null + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/AgpConfiguratorUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/AgpConfiguratorUtilsTest.kt new file mode 100644 index 00000000000000..1c9c07eb0b6fb7 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/AgpConfiguratorUtilsTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.utils + +import java.io.File +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class AgpConfiguratorUtilsTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun getPackageNameFromManifest_withEmptyFile_returnsNull() { + val mainFolder = tempFolder.newFolder("awesome-module/src/main/") + val manifest = File(mainFolder, "AndroidManifest.xml").apply { writeText("") } + + val actual = getPackageNameFromManifest(manifest) + assertNull(actual) + } + + @Test + fun getPackageNameFromManifest_withMissingPackage_returnsNull() { + val mainFolder = tempFolder.newFolder("awesome-module/src/main/") + val manifest = + File(mainFolder, "AndroidManifest.xml").apply { + writeText( + // language=xml + """ + + + """ + .trimIndent()) + } + + val actual = getPackageNameFromManifest(manifest) + assertNull(actual) + } + + @Test + fun getPackageNameFromManifest_withPackage_returnsPackage() { + val mainFolder = tempFolder.newFolder("awesome-module/src/main/") + val manifest = + File(mainFolder, "AndroidManifest.xml").apply { + writeText( + // language=xml + """ + + + """ + .trimIndent()) + } + + val actual = getPackageNameFromManifest(manifest) + assertNotNull(actual) + assertEquals("com.facebook.react", actual) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java index 9b3a7d7486ac34..79977623cde704 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java @@ -86,9 +86,6 @@ public BridgeDevSupportManager( surfaceDelegateFactory, devLoadingViewManager); - mReactInstanceManagerHelper = reactInstanceManagerHelper; - mDevLoadingViewManager = devLoadingViewManager; - if (getDevSettings().isStartSamplingProfilerOnInit()) { // Only start the profiler. If its already running, there is an error if (!mIsSamplingProfilerEnabled) { @@ -112,14 +109,6 @@ public void onOptionSelected() { }); } - public DevLoadingViewManager getDevLoadingViewManager() { - return mDevLoadingViewManager; - } - - public ReactInstanceDevHelper getReactInstanceManagerHelper() { - return mReactInstanceManagerHelper; - } - @Override protected String getUniqueTag() { return "Bridge"; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index bed2db92d8630f..57f0b9ac76fa1f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -695,7 +695,11 @@ public DevServerHelper getDevServerHelper() { return mDevServerHelper; } - protected ReactInstanceDevHelper getReactInstanceDevHelper() { + public DevLoadingViewManager getDevLoadingViewManager() { + return mDevLoadingViewManager; + } + + public ReactInstanceDevHelper getReactInstanceDevHelper() { return mReactInstanceDevHelper; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.java index f5e3cf13cb3b4c..8bd3731584de6c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.java @@ -13,8 +13,7 @@ import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.devsupport.BridgeDevSupportManager; -import com.facebook.react.devsupport.DefaultDevLoadingViewImplementation; +import com.facebook.react.devsupport.DevSupportManagerBase; import com.facebook.react.devsupport.interfaces.DevLoadingViewManager; import com.facebook.react.module.annotations.ReactModule; @@ -28,14 +27,9 @@ public class DevLoadingModule extends NativeDevLoadingViewSpec { public DevLoadingModule(ReactApplicationContext reactContext) { super(reactContext); mJSExceptionHandler = reactContext.getJSExceptionHandler(); - if (mJSExceptionHandler != null && mJSExceptionHandler instanceof BridgeDevSupportManager) { + if (mJSExceptionHandler != null && mJSExceptionHandler instanceof DevSupportManagerBase) { mDevLoadingViewManager = - ((BridgeDevSupportManager) mJSExceptionHandler).getDevLoadingViewManager(); - mDevLoadingViewManager = - mDevLoadingViewManager != null - ? mDevLoadingViewManager - : new DefaultDevLoadingViewImplementation( - ((BridgeDevSupportManager) mJSExceptionHandler).getReactInstanceManagerHelper()); + ((DevSupportManagerBase) mJSExceptionHandler).getDevLoadingViewManager(); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.java index 69cffd5cfa9229..7c23e701b430f1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.java @@ -9,13 +9,15 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.jni.HybridData; -import com.facebook.jni.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStripAny; +import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.modules.core.JavaScriptTimerExecutor; import com.facebook.soloader.SoLoader; @Nullsafe(Nullsafe.Mode.LOCAL) +@DoNotStripAny class JSTimerExecutor implements JavaScriptTimerExecutor { static { @@ -24,6 +26,7 @@ class JSTimerExecutor implements JavaScriptTimerExecutor { @DoNotStrip private final HybridData mHybridData; + @DoNotStrip public JSTimerExecutor(HybridData hybridData) { mHybridData = hybridData; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java index b8ed249d140798..494aeb61ff15a7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java @@ -638,6 +638,9 @@ DefaultHardwareBackBtnHandler getDefaultBackButtonHandler() { final String method = "handleHostException(message = \"" + e.getMessage() + "\")"; log(method); + if (DEV) { + mDevSupportManager.handleException(e); + } destroy(method, e); mReactHostDelegate.handleInstanceException(e); } @@ -922,6 +925,7 @@ private Task newGetOrCreateReactInstanceTask() { final JSBundleLoader bundleLoader = task.getResult(); final BridgelessReactContext reactContext = getOrCreateReactContext(); final DevSupportManager devSupportManager = getDevSupportManager(); + reactContext.setJSExceptionHandler(devSupportManager); log(method, "Creating ReactInstance"); final ReactInstance instance = @@ -1036,6 +1040,7 @@ private Task oldGetOrCreateReactInstanceTask() { final BridgelessReactContext reactContext = getOrCreateReactContext(); final DevSupportManager devSupportManager = getDevSupportManager(); + reactContext.setJSExceptionHandler(devSupportManager); return getJsBundleLoader() .onSuccess( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java index f97db18472926f..e129342bbc8513 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java @@ -71,6 +71,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -93,6 +94,7 @@ final class ReactInstance { private final TurboModuleManager mTurboModuleManager; private final FabricUIManager mFabricUIManager; private final JavaTimerManager mJavaTimerManager; + private final Map mViewManagers = new ConcurrentHashMap<>(); @DoNotStrip @Nullable private ComponentNameResolverManager mComponentNameResolverManager; @DoNotStrip @Nullable private UIConstantsProviderManager mUIConstantsProviderManager; @@ -489,8 +491,12 @@ public void registerSegment(int segmentId, String path) { } private @Nullable ViewManager createViewManager(String viewManagerName) { + // Return cached view manager if available, no matter it's eagerly or lazily loaded + if (mViewManagers.containsKey(viewManagerName)) { + return mViewManagers.get(viewManagerName); + } + List packages = mReactPackages; if (mDelegate != null) { - List packages = mReactPackages; if (packages != null) { synchronized (packages) { for (ReactPackage reactPackage : packages) { @@ -499,6 +505,7 @@ public void registerSegment(int segmentId, String path) { ((ViewManagerOnDemandReactPackage) reactPackage) .createViewManager(mBridgelessReactContext, viewManagerName); if (viewManager != null) { + mViewManagers.put(viewManagerName, viewManager); return viewManager; } } @@ -507,7 +514,17 @@ public void registerSegment(int segmentId, String path) { } } - return null; + // Once a view manager is not found in all react packages via lazy loading, fall back to default + // implementation: eagerly initialize all view managers + for (ReactPackage reactPackage : packages) { + List viewManagersInPackage = + reactPackage.createViewManagers(mBridgelessReactContext); + for (ViewManager viewManager : viewManagersInPackage) { + mViewManagers.put(viewManager.getName(), viewManager); + } + } + + return mViewManagers.get(viewManagerName); } private @NonNull Collection getViewManagerNames() { @@ -534,8 +551,28 @@ public void registerSegment(int segmentId, String path) { private @NonNull NativeMap getUIManagerConstants() { List viewManagers = new ArrayList(); - for (String viewManagerName : getViewManagerNames()) { - viewManagers.add(createViewManager(viewManagerName)); + boolean canLoadViewManagersLazily = true; + + List packages = mReactPackages; + for (ReactPackage reactPackage : packages) { + if (!(reactPackage instanceof ViewManagerOnDemandReactPackage)) { + canLoadViewManagersLazily = false; + break; + } + } + // 1, Retrive view managers via on demand loading + if (canLoadViewManagersLazily) { + for (String viewManagerName : getViewManagerNames()) { + viewManagers.add(createViewManager(viewManagerName)); + } + } else { + // 2, There are packages that don't implement ViewManagerOnDemandReactPackage so we retrieve + // view managers via eager loading + for (ReactPackage reactPackage : packages) { + List viewManagersInPackage = + reactPackage.createViewManagers(mBridgelessReactContext); + viewManagers.addAll(viewManagersInPackage); + } } Map constants = UIManagerModule.createConstants(viewManagers, new HashMap<>(), new HashMap<>()); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java index c4a3902d1e3d6a..e60538fda9f8d9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java @@ -22,9 +22,9 @@ import com.facebook.react.bridge.ReactNoCrashSoftException; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.UIManager; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.events.EventDispatcher; -import com.facebook.react.uimanager.events.EventDispatcherProvider; /** Helper class for {@link UIManager}. */ public class UIManagerHelper { @@ -53,13 +53,19 @@ private static UIManager getUIManager( @UIManagerType int uiManagerType, boolean returnNullIfCatalystIsInactive) { if (context.isBridgeless()) { - @Nullable UIManager uiManager = (UIManager) context.getJSIModule(JSIModuleType.UIManager); + UIManager uiManager = null; + if (uiManagerType == FABRIC) { + uiManager = (UIManager) context.getJSIModule(JSIModuleType.UIManager); + } else if (ReactFeatureFlags.unstable_useFabricInterop) { + // When Fabric Interop is enabled in Bridgeless mode, enable the legacy UIManager + uiManager = context.getNativeModule(UIManagerModule.class); + } + if (uiManager == null) { ReactSoftExceptionLogger.logSoftException( TAG, new ReactNoCrashSoftException( "Cannot get UIManager because the instance hasn't been initialized yet.")); - return null; } return uiManager; } @@ -118,13 +124,6 @@ public static EventDispatcher getEventDispatcherForReactTag(ReactContext context @Nullable public static EventDispatcher getEventDispatcher( ReactContext context, @UIManagerType int uiManagerType) { - // TODO T67518514 Clean this up once we migrate everything over to bridgeless mode - if (context.isBridgeless()) { - if (context instanceof ThemedReactContext) { - context = ((ThemedReactContext) context).getReactApplicationContext(); - } - return ((EventDispatcherProvider) context).getEventDispatcher(); - } UIManager uiManager = getUIManager(context, uiManagerType, false); if (uiManager == null) { ReactSoftExceptionLogger.logSoftException( diff --git a/packages/react-native/gradle/libs.versions.toml b/packages/react-native/gradle/libs.versions.toml index e6b790892f9f6f..de80b09d068505 100644 --- a/packages/react-native/gradle/libs.versions.toml +++ b/packages/react-native/gradle/libs.versions.toml @@ -16,7 +16,7 @@ assertj = "3.21.0" download = "5.4.0" fbjni = "0.5.1" flipper = "0.201.0" -fresco = "3.1.0" +fresco = "3.1.3" infer-annotation = "0.18.0" javax-inject = "1" jsr305 = "3.0.2"