diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -142,7 +142,7 @@ ${_react_native_dir}/ReactCommon/jsi/jsi/jsi.cpp ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp - ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/LongLivedObject.cpp + ${_react_native_dir}/ReactCommon/react/bridging/LongLivedObject.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.cpp # SQLCipher diff --git a/native/android/app/build.gradle b/native/android/app/build.gradle --- a/native/android/app/build.gradle +++ b/native/android/app/build.gradle @@ -81,9 +81,7 @@ project.ext.react = [ enableHermes: true, // clean and rebuild if changing - hermesCommand: new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/%OS-BIN%/hermesc", - cliPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/cli.js", - composeSourceMapsPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/scripts/compose-source-maps.js", + cliPath: ["node", "-e", "console.log(require('react-native/cli').bin);"].execute([], projectDir).text.trim(), ] apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle") @@ -126,9 +124,12 @@ def enableHermes = project.ext.react.get("enableHermes", false) /** - * Architectures to build native code for in debug. + * Architectures to build native code for. */ -def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures") +def reactNativeArchitectures() { + def value = project.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR") def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") @@ -271,12 +272,12 @@ task prebuildOpenSSL(dependsOn: dependenciesPath ? [] : [prepareOpenSSL]) { inputs.properties([ 'openssl.version': OPENSSL_VERSION, - 'openssl.abis': getBuildTypeABIs(nativeArchitectures) + 'openssl.abis': getBuildTypeABIs(reactNativeArchitectures()) ]) outputs.dir("${thirdPartyNdkDir}/openssl/openssl-${OPENSSL_VERSION}/build/") .withPropertyName('openssl.output') doFirst { - getBuildTypeABIs(nativeArchitectures).each { buildABI -> + getBuildTypeABIs(reactNativeArchitectures()).each { buildABI -> logger.info("Building OpenSSL library for the ${buildABI}") exec { commandLine './bash/build_openssl.sh', @@ -375,7 +376,7 @@ return allAbis } if (nativeArchitectures) { - return nativeArchitectures.split(',') + return nativeArchitectures } // Get current 'adb devices' architectures def commandOutput = new ByteArrayOutputStream() @@ -409,15 +410,74 @@ targetSdkVersion rootProject.ext.targetSdkVersion versionCode 166 versionName '1.0.166' + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + if (isNewArchitectureEnabled()) { + // We configure the CMake build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + cmake { + arguments "-DPROJECT_BUILD_DIR=$buildDir", + "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", + "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", + "-DNODE_MODULES_DIR=$rootDir/../node_modules", + "-DANDROID_STL=c++_shared" + } + } + if (!enableSeparateBuildPerCPUArchitecture) { + ndk { + abiFilters (*reactNativeArchitectures()) + } + } + } missingDimensionStrategy 'react-native-camera', 'general' multiDexEnabled true } + + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + cmake { + path "$projectDir/src/main/jni/CMakeLists.txt" + } + } + def reactAndroidProjectDir = project(':ReactAndroid').projectDir + def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + afterEvaluate { + // If you wish to add a custom TurboModule or component locally, + // you should uncomment this line. + // preBuild.dependsOn("generateCodegenArtifactsFromSchema") + preDebugBuild.dependsOn(packageReactNdkDebugLibs) + preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) + // Due to a bug inside AGP, we have to explicitly set a dependency + // between configureCMakeDebug* tasks and the preBuild tasks. + // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 + configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild) + configureCMakeDebug.dependsOn(preDebugBuild) + reactNativeArchitectures().each { architecture -> + tasks.findByName("configureCMakeDebug[${architecture}]")?.configure { + dependsOn("preDebugBuild") + } + tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure { + dependsOn("preReleaseBuild") + } + } + } + } + splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK - include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + include (*reactNativeArchitectures()) } } signingConfigs { @@ -438,7 +498,7 @@ } } buildTypes { - final buildABIs = getBuildTypeABIs(nativeArchitectures) + final buildABIs = getBuildTypeABIs(reactNativeArchitectures()) release { if (project.hasProperty('COMM_UPLOAD_STORE_FILE')) { signingConfig signingConfigs.release @@ -563,13 +623,31 @@ implementation 'org.conscrypt:conscrypt-android:2.0.0' if (enableHermes) { - debugImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-debug.aar")) - releaseImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-release.aar")) + //noinspection GradleDynamicVersion + implementation("com.facebook.react:hermes-engine:+") { // From node_modules + exclude group:'com.facebook.fbjni' + } } else { implementation jscFlavor } } +if (isNewArchitectureEnabled()) { + // If new architecture is enabled, we let you build RN from source + // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. + // This will be applied to all the imported transtitive dependency. + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("com.facebook.react:react-native")) + .using(project(":ReactAndroid")) + .because("On New Architecture we're building React Native from source") + substitute(module("com.facebook.react:hermes-engine")) + .using(project(":ReactAndroid:hermes-engine")) + .because("On New Architecture we're building Hermes from source") + } + } +} + // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { @@ -579,4 +657,13 @@ apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); applyNativeModulesAppBuildGradle(project) + +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file + // - Invoke gradle with `-newArchEnabled=true` + // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + apply plugin: 'com.google.gms.google-services' diff --git a/native/android/app/src/debug/AndroidManifest.xml b/native/android/app/src/debug/AndroidManifest.xml --- a/native/android/app/src/debug/AndroidManifest.xml +++ b/native/android/app/src/debug/AndroidManifest.xml @@ -9,6 +9,6 @@ android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning"> - + diff --git a/native/android/app/src/debug/java/app/comm/android/ReactNativeFlipper.java b/native/android/app/src/debug/java/app/comm/android/ReactNativeFlipper.java --- a/native/android/app/src/debug/java/app/comm/android/ReactNativeFlipper.java +++ b/native/android/app/src/debug/java/app/comm/android/ReactNativeFlipper.java @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -19,6 +19,7 @@ import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; @@ -51,7 +52,7 @@ ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); if (reactContext == null) { reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceManager.ReactInstanceEventListener() { + new ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext reactContext) { reactInstanceManager.removeReactInstanceEventListener(this); diff --git a/native/android/app/src/main/AndroidManifest.xml b/native/android/app/src/main/AndroidManifest.xml --- a/native/android/app/src/main/AndroidManifest.xml +++ b/native/android/app/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ android:name=".MainActivity" android:launchMode="singleTask" android:label="@string/app_name" - android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" + android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:windowSoftInputMode="adjustResize" android:exported="true" android:screenOrientation="portrait" diff --git a/native/android/app/src/main/java/app/comm/android/MainActivity.java b/native/android/app/src/main/java/app/comm/android/MainActivity.java --- a/native/android/app/src/main/java/app/comm/android/MainActivity.java +++ b/native/android/app/src/main/java/app/comm/android/MainActivity.java @@ -30,16 +30,43 @@ setIntent(intent); } + /** + * Returns the instance of the {@link ReactActivityDelegate}. There the + * RootView is created and you can specify the renderer you wish to use - the + * new renderer (Fabric) or the old renderer (Paper). + */ @Override protected ReactActivityDelegate createReactActivityDelegate() { return new ReactActivityDelegateWrapper( - this, new ReactActivityDelegate(this, getMainComponentName()) { + this, new MainActivityDelegate(this, getMainComponentName()) { @Override protected ReactRootView createRootView() { return new RNGestureHandlerEnabledRootView(MainActivity.this); } }); } + public static class MainActivityDelegate extends ReactActivityDelegate { + public MainActivityDelegate( + ReactActivity activity, + String mainComponentName) { + super(activity, mainComponentName); + } + @Override + protected ReactRootView createRootView() { + ReactRootView reactRootView = new ReactRootView(getContext()); + // If you opted-in for the New Architecture, we enable the Fabric + // Renderer. + reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); + return reactRootView; + } + @Override + protected boolean isConcurrentRootEnabled() { + // If you opted-in for the New Architecture, we enable Concurrent Root + // (i.e. React 18). More on this on + // https://reactjs.org/blog/2022/03/29/react-v18.html + return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + } + } @Override public void invokeDefaultOnBackPressed() { diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.java b/native/android/app/src/main/java/app/comm/android/MainApplication.java --- a/native/android/app/src/main/java/app/comm/android/MainApplication.java +++ b/native/android/app/src/main/java/app/comm/android/MainApplication.java @@ -5,12 +5,14 @@ import android.database.CursorWindow; import androidx.annotation.NonNull; import androidx.multidex.MultiDexApplication; +import app.comm.android.newarchitecture.MainApplicationReactNativeHost; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JSIModulePackage; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; import com.wix.reactnativekeyboardinput.KeyboardInputPackage; import expo.modules.ApplicationLifecycleDispatcher; @@ -57,15 +59,27 @@ } }); + private final ReactNativeHost mNewArchitectureNativeHost = + new MainApplicationReactNativeHost(this); + @Override public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return mNewArchitectureNativeHost; + } else { + return mReactNativeHost; + } } @Override public void onCreate() { super.onCreate(); + // If you opted-in for the New Architecture, we enable the TurboModule + // system + ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + Security.insertProviderAt(new org.conscrypt.OpenSSLProvider(), 1); + SoLoader.init(this, /* native exopackage */ false); initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); ApplicationLifecycleDispatcher.onApplicationCreate(this); diff --git a/native/android/app/src/main/java/app/comm/android/newarchitecture/MainApplicationReactNativeHost.java b/native/android/app/src/main/java/app/comm/android/newarchitecture/MainApplicationReactNativeHost.java new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/newarchitecture/MainApplicationReactNativeHost.java @@ -0,0 +1,118 @@ +package app.comm.android.newarchitecture; + +import android.app.Application; +import androidx.annotation.NonNull; +import app.comm.android.BuildConfig; +import app.comm.android.newarchitecture.components.MainComponentsRegistry; +import app.comm.android.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; +import com.facebook.react.PackageList; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.JSIModulePackage; +import com.facebook.react.bridge.JSIModuleProvider; +import com.facebook.react.bridge.JSIModuleSpec; +import com.facebook.react.bridge.JSIModuleType; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.react.fabric.CoreComponentsRegistry; +import com.facebook.react.fabric.FabricJSIModuleProvider; +import com.facebook.react.fabric.ReactNativeConfig; +import com.facebook.react.uimanager.ViewManagerRegistry; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link ReactNativeHost} that helps you load everything needed for the New + * Architecture, both TurboModule delegates and the Fabric Renderer. + * + *

Please note that this class is used ONLY if you opt-in for the New + * Architecture (see the `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationReactNativeHost extends ReactNativeHost { + public MainApplicationReactNativeHost(Application application) { + super(application); + } + + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for + // example: + // packages.add(new MyReactNativePackage()); + // TurboModules must also be loaded here providing a valid TurboReactPackage + // implementation: + // packages.add(new TurboReactPackage() { ... }); + // If you have custom Fabric Components, their ViewManagers should also be + // loaded here inside a ReactPackage. + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @NonNull + @Override + protected ReactPackageTurboModuleManagerDelegate.Builder + getReactPackageTurboModuleManagerDelegateBuilder() { + // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This + // is necessary for the new architecture and to use TurboModules correctly. + return new MainApplicationTurboModuleManagerDelegate.Builder(); + } + + @Override + protected JSIModulePackage getJSIModulePackage() { + return new JSIModulePackage() { + @Override + public List getJSIModules( + final ReactApplicationContext reactApplicationContext, + final JavaScriptContextHolder jsContext) { + final List specs = new ArrayList<>(); + + // Here we provide a new JSIModuleSpec that will be responsible of + // providing the custom Fabric Components. + specs.add(new JSIModuleSpec() { + @Override + public JSIModuleType getJSIModuleType() { + return JSIModuleType.UIManager; + } + + @Override + public JSIModuleProvider getJSIModuleProvider() { + final ComponentFactory componentFactory = new ComponentFactory(); + CoreComponentsRegistry.register(componentFactory); + + // Here we register a Components Registry. + // The one that is generated with the template contains no + // components and just provides you the one from React Native core. + MainComponentsRegistry.register(componentFactory); + + final ReactInstanceManager reactInstanceManager = + getReactInstanceManager(); + + ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry( + reactInstanceManager.getOrCreateViewManagers( + reactApplicationContext)); + + return new FabricJSIModuleProvider( + reactApplicationContext, + componentFactory, + ReactNativeConfig.DEFAULT_CONFIG, + viewManagerRegistry); + } + }); + return specs; + } + }; + } +} diff --git a/native/android/app/src/main/java/app/comm/android/newarchitecture/components/MainComponentsRegistry.java b/native/android/app/src/main/java/app/comm/android/newarchitecture/components/MainComponentsRegistry.java new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/newarchitecture/components/MainComponentsRegistry.java @@ -0,0 +1,37 @@ +package app.comm.android.newarchitecture.components; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.soloader.SoLoader; + +/** + * Class responsible to load the custom Fabric Components. This class has native + * methods and needs a corresponding C++ implementation/header file to work + * correctly (already placed inside the jni/ folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New + * Architecture (see the `newArchEnabled` property). Is ignored otherwise. + */ +@DoNotStrip +public class MainComponentsRegistry { + static { + SoLoader.loadLibrary("fabricjni"); + } + + @DoNotStrip private final HybridData mHybridData; + + @DoNotStrip + private native HybridData initHybrid(ComponentFactory componentFactory); + + @DoNotStrip + private MainComponentsRegistry(ComponentFactory componentFactory) { + mHybridData = initHybrid(componentFactory); + } + + @DoNotStrip + public static MainComponentsRegistry register( + ComponentFactory componentFactory) { + return new MainComponentsRegistry(componentFactory); + } +} diff --git a/native/android/app/src/main/java/app/comm/android/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/native/android/app/src/main/java/app/comm/android/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java @@ -0,0 +1,50 @@ +package app.comm.android.newarchitecture.modules; + +import com.facebook.jni.HybridData; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.soloader.SoLoader; +import java.util.List; + +/** + * Class responsible to load the TurboModules. This class has native methods and + * needs a corresponding C++ implementation/header file to work correctly + * (already placed inside the jni/ folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New + * Architecture (see the `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationTurboModuleManagerDelegate + extends ReactPackageTurboModuleManagerDelegate { + + private static volatile boolean sIsSoLibraryLoaded; + + protected MainApplicationTurboModuleManagerDelegate( + ReactApplicationContext reactApplicationContext, + List packages) { + super(reactApplicationContext, packages); + } + + protected native HybridData initHybrid(); + + native boolean canCreateTurboModule(String moduleName); + + public static class Builder + extends ReactPackageTurboModuleManagerDelegate.Builder { + protected MainApplicationTurboModuleManagerDelegate + build(ReactApplicationContext context, List packages) { + return new MainApplicationTurboModuleManagerDelegate(context, packages); + } + } + + @Override + protected synchronized void maybeLoadOtherSoLibraries() { + if (!sIsSoLibraryLoaded) { + // If you change the name of your application .so file in the Android.mk + // file, make sure you update the name here as well. + SoLoader.loadLibrary("comm_appmodules"); + sIsSoLibraryLoaded = true; + } + } +} diff --git a/native/android/app/src/main/jni/CMakeLists.txt b/native/android/app/src/main/jni/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13) +# Define the library name here. +project(comm_appmodules) +# This file includes all the necessary to +# let you build your application with the New Architecture. +include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake) diff --git a/native/android/app/src/main/jni/MainApplicationModuleProvider.h b/native/android/app/src/main/jni/MainApplicationModuleProvider.h new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainApplicationModuleProvider.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string &moduleName, + const JavaTurboModule::InitParams ¶ms); + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/native/android/app/src/main/jni/MainApplicationModuleProvider.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainApplicationModuleProvider.cpp @@ -0,0 +1,32 @@ +#include "MainApplicationModuleProvider.h" + +#include +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string &moduleName, + const JavaTurboModule::InitParams ¶ms) { + // Here you can provide your own module provider for TurboModules coming from + // either your application or from external libraries. The approach to follow + // is similar to the following (for a library called `samplelibrary`: + // + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + // return rncore_ModuleProvider(moduleName, params); + + // Module providers autolinked by RN CLI + auto rncli_module = rncli_ModuleProvider(moduleName, params); + if (rncli_module != nullptr) { + return rncli_module; + } + + return rncore_ModuleProvider(moduleName, params); +} + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h @@ -0,0 +1,38 @@ +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class MainApplicationTurboModuleManagerDelegate + : public jni::HybridClass< + MainApplicationTurboModuleManagerDelegate, + TurboModuleManagerDelegate> { + public: + // Adapt it to the package you used for your Java class. + static constexpr auto kJavaDescriptor = + "Lapp/comm/android/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; + + static jni::local_ref initHybrid(jni::alias_ref); + + static void registerNatives(); + + std::shared_ptr getTurboModule( + const std::string &name, + const std::shared_ptr &jsInvoker) override; + std::shared_ptr getTurboModule( + const std::string &name, + const JavaTurboModule::InitParams ¶ms) override; + + /** + * Test-only method. Allows user to verify whether a TurboModule can be + * created by instances of this class. + */ + bool canCreateTurboModule(const std::string &name); +}; + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp @@ -0,0 +1,45 @@ +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainApplicationModuleProvider.h" + +namespace facebook { +namespace react { + +jni::local_ref +MainApplicationTurboModuleManagerDelegate::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void MainApplicationTurboModuleManagerDelegate::registerNatives() { + registerHybrid({ + makeNativeMethod( + "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), + makeNativeMethod( + "canCreateTurboModule", + MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), + }); +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string &name, + const std::shared_ptr &jsInvoker) { + // Not implemented yet: provide pure-C++ NativeModules here. + return nullptr; +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string &name, + const JavaTurboModule::InitParams ¶ms) { + return MainApplicationModuleProvider(name, params); +} + +bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( + const std::string &name) { + return getTurboModule(name, nullptr) != nullptr || + getTurboModule(name, {.moduleName = name}) != nullptr; +} + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/MainComponentsRegistry.h b/native/android/app/src/main/jni/MainComponentsRegistry.h new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainComponentsRegistry.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class MainComponentsRegistry + : public facebook::jni::HybridClass { + public: + // Adapt it to the package you used for your Java class. + constexpr static auto kJavaDescriptor = + "Lapp/comm/android/newarchitecture/components/MainComponentsRegistry;"; + + static void registerNatives(); + + MainComponentsRegistry(ComponentFactory *delegate); + + private: + static std::shared_ptr + sharedProviderRegistry(); + + static jni::local_ref initHybrid( + jni::alias_ref, + ComponentFactory *delegate); +}; + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/MainComponentsRegistry.cpp b/native/android/app/src/main/jni/MainComponentsRegistry.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/MainComponentsRegistry.cpp @@ -0,0 +1,65 @@ +#include "MainComponentsRegistry.h" + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} + +std::shared_ptr +MainComponentsRegistry::sharedProviderRegistry() { + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + // Autolinked providers registered by RN CLI + rncli_registerProviders(providerRegistry); + + // Custom Fabric Components go here. You can register custom + // components coming from your App or from 3rd party libraries here. + // + // providerRegistry->add(concreteComponentDescriptorProvider< + // AocViewerComponentDescriptor>()); + return providerRegistry; +} + +jni::local_ref +MainComponentsRegistry::initHybrid( + jni::alias_ref, + ComponentFactory *delegate) { + auto instance = makeCxxInstance(delegate); + + auto buildRegistryFunction = + [](EventDispatcher::Weak const &eventDispatcher, + ContextContainer::Shared const &contextContainer) + -> ComponentDescriptorRegistry::Shared { + auto registry = MainComponentsRegistry::sharedProviderRegistry() + ->createComponentDescriptorRegistry( + {eventDispatcher, contextContainer}); + + auto mutableRegistry = + std::const_pointer_cast(registry); + + mutableRegistry->setFallbackComponentDescriptor( + std::make_shared( + ComponentDescriptorParameters{ + eventDispatcher, contextContainer, nullptr})); + + return registry; + }; + + delegate->buildRegistryFunction = buildRegistryFunction; + return instance; +} + +void MainComponentsRegistry::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), + }); +} + +} // namespace react +} // namespace facebook diff --git a/native/android/app/src/main/jni/OnLoad.cpp b/native/android/app/src/main/jni/OnLoad.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/jni/OnLoad.cpp @@ -0,0 +1,11 @@ +#include +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainComponentsRegistry.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] { + facebook::react::MainApplicationTurboModuleManagerDelegate:: + registerNatives(); + facebook::react::MainComponentsRegistry::registerNatives(); + }); +} diff --git a/native/android/build.gradle b/native/android/build.gradle --- a/native/android/build.gradle +++ b/native/android/build.gradle @@ -2,10 +2,19 @@ buildscript { ext { - buildToolsVersion = "30.0.2" + buildToolsVersion = "31.0.0" minSdkVersion = 21 compileSdkVersion = 31 targetSdkVersion = 31 + // We can't use NDK 24 yet because of Folly build issues + // https://linear.app/comm/issue/ENG-2169/update-folly-on-android + //if (System.properties['os.arch'] == "aarch64") { + // // For M1 Users we need to use the NDK 24 which added support for aarch64 + // ndkVersion = "24.0.8215888" + //} else { + // // Otherwise we default to the side-by-side NDK version from AGP. + // ndkVersion = "21.4.7075529" + //} ndkVersion = "21.4.7075529" } repositories { @@ -13,9 +22,10 @@ mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:4.2.2") - classpath("de.undercouch:gradle-download-task:4.0.2") - classpath("com.google.gms:google-services:4.2.0") + classpath("com.android.tools.build:gradle:7.2.1") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("de.undercouch:gradle-download-task:5.0.1") + classpath("com.google.gms:google-services:4.3.14") classpath('org.ajoberstar:grgit:1.7.2') // NOTE: Do not place your application dependencies here; they belong @@ -29,7 +39,6 @@ allprojects { repositories { - mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../android")) diff --git a/native/android/gradle.properties b/native/android/gradle.properties --- a/native/android/gradle.properties +++ b/native/android/gradle.properties @@ -9,8 +9,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx1024m -XX:MaxPermSize=256m -org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit @@ -35,7 +35,19 @@ expo.webp.animated=false # Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.99.0 +FLIPPER_VERSION=0.125.0 + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=false FOLLY_VERSION=2020.01.13.00 GLOG_VERSION=0.4.0 @@ -44,7 +56,3 @@ # Version of OpenSSL library to build and link OPENSSL_VERSION=1.1.1l - -# Set to something like below to build specific architectures: -# reactNativeDebugArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 -reactNativeDebugArchitectures= diff --git a/native/android/gradle/wrapper/gradle-wrapper.jar b/native/android/gradle/wrapper/gradle-wrapper.jar index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/native/android/settings.gradle b/native/android/settings.gradle --- a/native/android/settings.gradle +++ b/native/android/settings.gradle @@ -10,6 +10,14 @@ project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android') include ':app' +includeBuild('../../node_modules/react-native-gradle-plugin') + +if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { + include(":ReactAndroid") + project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid') + include(":ReactAndroid:hermes-engine") + project(":ReactAndroid:hermes-engine").projectDir = file('../node_modules/react-native/ReactAndroid/hermes-engine') +} // We need to include '.gitmodules' for our 'GitModules' custom task org.apache.tools.ant.DirectoryScanner.removeDefaultExclude("**/.gitmodules")