feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,8 @@
# Building React Native for Android
See the [Building from Source guide](https://reactnative.dev/contributing/how-to-build-from-source#prerequisites) on the React Native website.
# Running tests
When you submit a pull request CircleCI will automatically run all tests.
To run tests locally, see [Testing guide](https://reactnative.dev/contributing/how-to-run-and-write-tests) on the React Native website.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,813 @@
/*
* 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.
*/
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.internal.*
import com.facebook.react.tasks.internal.utils.*
import de.undercouch.gradle.tasks.download.Download
import java.nio.file.Paths
plugins {
id("maven-publish")
id("com.facebook.react")
alias(libs.plugins.android.library)
alias(libs.plugins.download)
alias(libs.plugins.kotlin.android)
}
version = project.findProperty("VERSION_NAME")?.toString()!!
group = "com.facebook.react"
// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
val buildDir = project.layout.buildDirectory.get().asFile
val downloadsDir =
if (System.getenv("REACT_NATIVE_DOWNLOADS_DIR") != null) {
File(System.getenv("REACT_NATIVE_DOWNLOADS_DIR"))
} else {
File("$buildDir/downloads")
}
val thirdPartyNdkDir = File("$buildDir/third-party-ndk")
val reactNativeRootDir = projectDir.parent
// We put the publishing version from gradle.properties inside ext. so other
// subprojects can access it as well.
extra["publishing_version"] = project.findProperty("VERSION_NAME")?.toString()!!
// This is the version of CMake we're requesting to the Android SDK to use.
// If missing it will be downloaded automatically. Only CMake versions shipped with the
// Android SDK are supported (you can find them listed in the SDK Manager of Android Studio).
val cmakeVersion = System.getenv("CMAKE_VERSION") ?: "3.22.1"
extra["cmake_version"] = cmakeVersion
// You need to have following folders in this directory:
// - boost_1_83_0
// - double-conversion-1.1.6
// - folly-deprecate-dynamic-initializer
// - glog-0.3.5
val dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES")
// The Boost library is a very large download (>100MB).
// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable
// and the build will use that.
val boostPathOverride = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH")
val prefabHeadersDir = project.file("$buildDir/prefab-headers")
// Native versions which are defined inside the version catalog (libs.versions.toml)
val BOOST_VERSION = libs.versions.boost.get()
val DOUBLE_CONVERSION_VERSION = libs.versions.doubleconversion.get()
val FMT_VERSION = libs.versions.fmt.get()
val FOLLY_VERSION = libs.versions.folly.get()
val GLOG_VERSION = libs.versions.glog.get()
val GTEST_VERSION = libs.versions.gtest.get()
val preparePrefab by
tasks.registering(PreparePrefabHeadersTask::class) {
dependsOn(prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog)
dependsOn("generateCodegenArtifactsFromSchema")
// To export to a ReactNativePrefabProcessingEntities.kt once all
// libraries have been moved. We keep it here for now as it make easier to
// migrate one library at a time.
input.set(
listOf(
PrefabPreprocessingEntry(
"react_render_debug",
Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/")),
PrefabPreprocessingEntry(
"turbomodulejsijni", Pair("src/main/jni/react/turbomodule", "")),
PrefabPreprocessingEntry(
"runtimeexecutor", Pair("../ReactCommon/runtimeexecutor/", "")),
PrefabPreprocessingEntry(
"react_codegen_rncore",
Pair(File(buildDir, "generated/source/codegen/jni/").absolutePath, "")),
PrefabPreprocessingEntry(
"react_debug", Pair("../ReactCommon/react/debug/", "react/debug/")),
PrefabPreprocessingEntry(
"react_render_componentregistry",
Pair(
"../ReactCommon/react/renderer/componentregistry/",
"react/renderer/componentregistry/")),
PrefabPreprocessingEntry(
"react_newarchdefaults", Pair("src/main/jni/react/newarchdefaults", "")),
PrefabPreprocessingEntry(
"react_cxxreactpackage", Pair("src/main/jni/react/runtime/cxxreactpackage", "")),
PrefabPreprocessingEntry(
"react_render_animations",
Pair("../ReactCommon/react/renderer/animations/", "react/renderer/animations/")),
PrefabPreprocessingEntry(
"react_render_core",
Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/")),
PrefabPreprocessingEntry(
"react_render_graphics",
listOf(
Pair("../ReactCommon/react/renderer/graphics/", "react/renderer/graphics/"),
Pair("../ReactCommon/react/renderer/graphics/platform/android/", ""),
)),
PrefabPreprocessingEntry(
"rrc_root",
Pair(
"../ReactCommon/react/renderer/components/root/",
"react/renderer/components/root/")),
PrefabPreprocessingEntry(
"rrc_view",
listOf(
Pair(
"../ReactCommon/react/renderer/components/view/",
"react/renderer/components/view/"),
Pair("../ReactCommon/react/renderer/components/view/platform/android/", ""),
)),
PrefabPreprocessingEntry(
"rrc_text",
listOf(
Pair(
"../ReactCommon/react/renderer/components/text/",
"react/renderer/components/text/"),
Pair(
"../ReactCommon/react/renderer/attributedstring",
"react/renderer/attributedstring"),
)),
PrefabPreprocessingEntry(
"rrc_textinput",
listOf(
Pair(
"../ReactCommon/react/renderer/components/textinput/",
"react/renderer/components/textinput/"),
Pair(
"../ReactCommon/react/renderer/components/textinput/platform/android/",
""),
)),
PrefabPreprocessingEntry(
"rrc_legacyviewmanagerinterop",
Pair(
"../ReactCommon/react/renderer/components/legacyviewmanagerinterop/",
"react/renderer/components/legacyviewmanagerinterop/")),
PrefabPreprocessingEntry("jsi", Pair("../ReactCommon/jsi/", "")),
PrefabPreprocessingEntry(
"glog", Pair(File(buildDir, "third-party-ndk/glog/exported/").absolutePath, "")),
PrefabPreprocessingEntry(
"fabricjni", Pair("src/main/jni/react/fabric", "react/fabric/")),
PrefabPreprocessingEntry(
"react_render_mapbuffer",
Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/")),
PrefabPreprocessingEntry(
"react_render_textlayoutmanager",
listOf(
Pair(
"../ReactCommon/react/renderer/textlayoutmanager/",
"react/renderer/textlayoutmanager/"),
Pair("../ReactCommon/react/renderer/textlayoutmanager/platform/android/", ""),
)),
PrefabPreprocessingEntry(
"yoga",
listOf(
Pair("../ReactCommon/yoga/", ""),
Pair("src/main/jni/first-party/yogajni/jni", ""))),
PrefabPreprocessingEntry(
"folly_runtime",
listOf(
Pair(File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/folly/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""),
)),
PrefabPreprocessingEntry(
"react_nativemodule_core",
listOf(
Pair(File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/folly/").absolutePath, ""),
Pair(File(buildDir, "third-party-ndk/glog/exported/").absolutePath, ""),
Pair("../ReactCommon/butter/", "butter/"),
Pair("../ReactCommon/callinvoker/", ""),
Pair("../ReactCommon/cxxreact/", "cxxreact/"),
Pair("../ReactCommon/react/bridging/", "react/bridging/"),
Pair("../ReactCommon/react/config/", "react/config/"),
Pair("../ReactCommon/react/nativemodule/core/", ""),
Pair("../ReactCommon/react/nativemodule/core/platform/android/", ""),
Pair(
"../ReactCommon/react/renderer/componentregistry/",
"react/renderer/componentregistry/"),
Pair(
"../ReactCommon/react/renderer/components/root/",
"react/renderer/components/root/"),
Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/"),
Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/"),
Pair(
"../ReactCommon/react/renderer/leakchecker/",
"react/renderer/leakchecker/"),
Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/"),
Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"),
Pair(
"../ReactCommon/react/renderer/runtimescheduler/",
"react/renderer/runtimescheduler/"),
Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"),
Pair("../ReactCommon/react/renderer/telemetry/", "react/renderer/telemetry/"),
Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"),
Pair("../ReactCommon/react/debug/", "react/debug/"),
Pair("../ReactCommon/react/utils/", "react/utils/"),
Pair("src/main/jni/react/jni", "react/jni/"),
)),
PrefabPreprocessingEntry(
"react_utils",
Pair("../ReactCommon/react/utils/", "react/utils/"),
),
PrefabPreprocessingEntry(
"react_render_imagemanager",
listOf(
Pair(
"../ReactCommon/react/renderer/imagemanager/",
"react/renderer/imagemanager/"),
Pair("../ReactCommon/react/renderer/imagemanager/platform/cxx/", ""),
)),
PrefabPreprocessingEntry(
"rrc_image",
Pair(
"../ReactCommon/react/renderer/components/image/",
"react/renderer/components/image/")),
// These prefab targets are used by Expo & Reanimated
PrefabPreprocessingEntry(
"hermes_executor",
// "hermes_executor" is statically linking against "hermes_executor_common"
// and "hermes_inspector_modern". Here we expose only the headers that we know are
// needed.
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/")),
PrefabPreprocessingEntry(
"jscexecutor",
// "jscexecutor" is statically linking against "jscruntime"
// Here we expose only the headers that we know are needed.
Pair("../ReactCommon/jsc/", "jsc/")),
PrefabPreprocessingEntry(
"react_render_uimanager",
Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"),
),
PrefabPreprocessingEntry(
"react_render_scheduler",
Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"),
),
PrefabPreprocessingEntry(
"react_render_mounting",
Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"),
),
PrefabPreprocessingEntry(
"reactnativejni",
listOf(
Pair("src/main/jni/react/jni", "react/jni/"),
Pair("../ReactCommon/cxxreact/", "cxxreact/"),
)),
PrefabPreprocessingEntry(
"jsinspector",
Pair("../ReactCommon/jsinspector-modern/", "jsinspector-modern/"),
),
))
outputDir.set(prefabHeadersDir)
}
val createNativeDepsDirectories by
tasks.registering {
downloadsDir.mkdirs()
thirdPartyNdkDir.mkdirs()
}
val downloadBoost by
tasks.creating(Download::class) {
dependsOn(createNativeDepsDirectories)
src(
"https://archives.boost.io/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz"))
}
val prepareBoost by
tasks.registering(PrepareBoostTask::class) {
dependsOn(if (boostPathOverride != null) emptyList() else listOf(downloadBoost))
boostPath.setFrom(if (boostPathOverride != null) boostPath else tarTree(downloadBoost.dest))
boostVersion.set(BOOST_VERSION)
outputDir.set(File(thirdPartyNdkDir, "boost"))
}
val downloadDoubleConversion by
tasks.creating(Download::class) {
dependsOn(createNativeDepsDirectories)
src(
"https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz"))
}
val prepareDoubleConversion by
tasks.registering(Copy::class) {
dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadDoubleConversion))
from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest))
from("src/main/jni/third-party/double-conversion/")
include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "CMakeLists.txt")
filesMatching("*/src/**/*") { this.path = "double-conversion/${this.name}" }
includeEmptyDirs = false
into("$thirdPartyNdkDir/double-conversion")
}
val downloadFolly by
tasks.creating(Download::class) {
src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz"))
}
val prepareFolly by
tasks.registering(Copy::class) {
dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadFolly))
from(dependenciesPath ?: tarTree(downloadFolly.dest))
from("src/main/jni/third-party/folly/")
include("folly-${FOLLY_VERSION}/folly/**/*", "CMakeLists.txt")
eachFile { this.path = this.path.removePrefix("folly-${FOLLY_VERSION}/") }
includeEmptyDirs = false
into("$thirdPartyNdkDir/folly")
}
val downloadFmt by
tasks.creating(Download::class) {
dependsOn(createNativeDepsDirectories)
src("https://github.com/fmtlib/fmt/archive/${FMT_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "fmt-${FMT_VERSION}.tar.gz"))
}
val prepareFmt by
tasks.registering(Copy::class) {
dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadFmt))
from(dependenciesPath ?: tarTree(downloadFmt.dest))
from("src/main/jni/third-party/fmt/")
include("fmt-${FMT_VERSION}/src/**/*", "fmt-${FMT_VERSION}/include/**/*", "CMakeLists.txt")
eachFile { this.path = this.path.removePrefix("fmt-${FMT_VERSION}/") }
includeEmptyDirs = false
into("$thirdPartyNdkDir/fmt")
}
val downloadGlog by
tasks.creating(Download::class) {
dependsOn(createNativeDepsDirectories)
src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz"))
}
val downloadGtest by
tasks.creating(Download::class) {
dependsOn(createNativeDepsDirectories)
src("https://github.com/google/googletest/archive/refs/tags/release-${GTEST_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
dest(File(downloadsDir, "gtest.tar.gz"))
}
val prepareGtest by
tasks.registering(Copy::class) {
dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadGtest))
from(dependenciesPath ?: tarTree(downloadGtest.dest))
eachFile { this.path = (this.path.removePrefix("googletest-release-${GTEST_VERSION}/")) }
into(File(thirdPartyNdkDir, "googletest"))
}
// Prepare glog sources to be compiled, this task will perform steps that normally should've been
// executed by automake. This way we can avoid dependencies on make/automake
val prepareGlog by
tasks.registering(PrepareGlogTask::class) {
dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadGlog))
glogPath.setFrom(dependenciesPath ?: tarTree(downloadGlog.dest))
glogVersion.set(GLOG_VERSION)
outputDir.set(File(thirdPartyNdkDir, "glog"))
}
// Create Android native library module based on jsc from npm
val prepareJSC by
tasks.registering(PrepareJSCTask::class) {
jscPackagePath.set(findNodeModulePath(projectDir, "jsc-android"))
outputDir = project.layout.buildDirectory.dir("third-party-ndk/jsc")
}
val prepareKotlinBuildScriptModel by
tasks.registering {
// This task is run when Gradle Sync is running.
// We create it here so we can let it depend on preBuild inside the android{}
}
// As ReactAndroid builds from source, the codegen needs to be built before it can be invoked.
// This is not the case for users of React Native, as we ship a compiled version of the codegen.
val buildCodegenCLI by
tasks.registering(BuildCodegenCLITask::class) {
codegenDir.set(file("$rootDir/node_modules/@react-native/codegen"))
bashWindowsHome.set(project.findProperty("react.internal.windowsBashPath").toString())
onlyIf {
// For build from source scenario, we don't need to build the codegen at all.
rootProject.name != "react-native-build-from-source"
}
}
/**
* Finds the path of the installed npm package with the given name using Node's module resolution
* algorithm, which searches "node_modules" directories up to the file system root. This handles
* various cases, including:
* - Working in the open-source RN repo: Gradle: /path/to/react-native/ReactAndroid Node module:
* /path/to/react-native/node_modules/<package>
* - Installing RN as a dependency of an app and searching for hoisted dependencies: Gradle:
* /path/to/app/node_modules/react-native/ReactAndroid Node module:
* /path/to/app/node_modules/<package>
* - Working in a larger repo (e.g., Facebook) that contains RN: Gradle:
* /path/to/repo/path/to/react-native/ReactAndroid Node module:
* /path/to/repo/node_modules/<package>
*
* The search begins at the given base directory (a File object). The returned path is a string.
*/
fun findNodeModulePath(baseDir: File, packageName: String): String? {
var basePath: java.nio.file.Path? = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath != null) {
val candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.parent
}
return null
}
fun reactNativeDevServerPort(): String {
val value = project.properties["reactNativeDevServerPort"]
return value?.toString() ?: "8081"
}
fun reactNativeArchitectures(): List<String> {
val value = project.properties["reactNativeArchitectures"]
return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
}
fun enableWarningsAsErrors(): Boolean {
val value = project.properties["enableWarningsAsErrors"]
return value?.toString()?.toBoolean() ?: false
}
val packageReactNdkLibsForBuck by
tasks.registering(Copy::class) {
dependsOn("mergeDebugNativeLibs")
// Shared libraries (.so) are copied from the merged_native_libs folder instead
from("$buildDir/intermediates/merged_native_libs/debug/out/lib/")
exclude("**/libjsc.so")
exclude("**/libhermes.so")
into("src/main/jni/prebuilt/lib")
}
repositories {
// Normally RNGP will set repositories for all modules,
// but when consumed from source, we need to re-declare
// those repositories as there is no app module there.
mavenCentral()
google()
}
android {
compileSdk = libs.versions.compileSdk.get().toInt()
buildToolsVersion = libs.versions.buildTools.get()
namespace = "com.facebook.react"
// Used to override the NDK path/version on internal CI or by allowing
// users to customize the NDK path/version from their root project (e.g. for Apple Silicon
// support)
if (rootProject.hasProperty("ndkPath") && rootProject.properties["ndkPath"] != null) {
ndkPath = rootProject.properties["ndkPath"].toString()
}
if (rootProject.hasProperty("ndkVersion") && rootProject.properties["ndkVersion"] != null) {
ndkVersion = rootProject.properties["ndkVersion"].toString()
} else {
ndkVersion = libs.versions.ndkVersion.get()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
// Using '-Xjvm-default=all' to generate default java methods for interfaces
freeCompilerArgs = listOf("-Xjvm-default=all")
// Using -PenableWarningsAsErrors=true prop to enable allWarningsAsErrors
kotlinOptions.allWarningsAsErrors = enableWarningsAsErrors()
}
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
consumerProguardFiles("proguard-rules.pro")
buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
resValue("integer", "react_native_dev_server_port", reactNativeDevServerPort())
testApplicationId = "com.facebook.react.tests.gradle"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
arguments(
"-DREACT_COMMON_DIR=${reactNativeRootDir}/ReactCommon",
"-DREACT_ANDROID_DIR=$projectDir",
"-DREACT_BUILD_DIR=$buildDir",
"-DANDROID_STL=c++_shared",
"-DANDROID_TOOLCHAIN=clang",
// Due to https://github.com/android/ndk/issues/1693 we're losing Android
// specific compilation flags. This can be removed once we moved to NDK 25/26
"-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON")
targets(
"jsijniprofiler",
"reactnativeblob",
"reactperfloggerjni",
"bridgeless",
"rninstance",
"hermesinstancejni",
"uimanagerjni",
"jscinstance",
"react_devsupportjni",
// prefab targets
"reactnativejni",
"react_render_debug",
"turbomodulejsijni",
"runtimeexecutor",
"react_featureflagsjni",
"react_codegen_rncore",
"react_debug",
"react_featureflags",
"react_utils",
"react_render_componentregistry",
"react_newarchdefaults",
"react_cxxreactpackage",
"react_render_animations",
"react_render_core",
"react_render_graphics",
"rrc_image",
"rrc_root",
"rrc_view",
"rrc_text",
"rrc_textinput",
"rrc_legacyviewmanagerinterop",
"jsi",
"glog",
"fabricjni",
"react_render_mapbuffer",
"react_render_textlayoutmanager",
"yoga",
"folly_runtime",
"react_nativemodule_core",
"react_render_imagemanager",
"react_render_uimanager",
"react_render_scheduler",
"react_render_mounting",
"hermes_executor",
"jscexecutor",
"jsinspector")
}
}
ndk { abiFilters.addAll(reactNativeArchitectures()) }
}
externalNativeBuild {
cmake {
version = cmakeVersion
path("src/main/jni/CMakeLists.txt")
}
}
buildTypes {
debug {
externalNativeBuild {
cmake {
// We want to build Gtest suite only for the debug variant.
targets("reactnative_unittest")
}
}
}
}
tasks
.getByName("preBuild")
.dependsOn(
buildCodegenCLI,
"generateCodegenArtifactsFromSchema",
prepareBoost,
prepareDoubleConversion,
prepareFmt,
prepareFolly,
prepareGlog,
prepareGtest,
prepareJSC,
preparePrefab)
tasks.getByName("generateCodegenSchemaFromJavaScript").dependsOn(buildCodegenCLI)
prepareKotlinBuildScriptModel.dependsOn("preBuild")
prepareKotlinBuildScriptModel.dependsOn(
":packages:react-native:ReactAndroid:hermes-engine:preBuild")
sourceSets.getByName("main") {
res.setSrcDirs(
listOf(
"src/main/res/devsupport",
"src/main/res/shell",
"src/main/res/views/modal",
"src/main/res/views/uimanager"))
java.exclude("com/facebook/annotationprocessors")
java.exclude("com/facebook/react/processing")
java.exclude("com/facebook/react/module/processing")
}
lint {
abortOnError = false
targetSdk = libs.versions.targetSdk.get().toInt()
}
packaging {
resources.excludes.add("META-INF/NOTICE")
resources.excludes.add("META-INF/LICENSE")
// We intentionally don't want to bundle any JS Runtime inside the Android AAR
// we produce. The reason behind this is that we want to allow users to pick the
// JS engine by specifying a dependency on either `hermes-engine` or `android-jsc`
// that will include the necessary .so files to load.
jniLibs.excludes.add("**/libhermes.so")
jniLibs.excludes.add("**/libjsc.so")
}
buildFeatures {
prefab = true
prefabPublishing = true
buildConfig = true
}
prefab {
create("react_render_debug") {
headers = File(prefabHeadersDir, "react_render_debug").absolutePath
}
create("turbomodulejsijni") {
headers = File(prefabHeadersDir, "turbomodulejsijni").absolutePath
}
create("runtimeexecutor") { headers = File(prefabHeadersDir, "runtimeexecutor").absolutePath }
create("react_codegen_rncore") {
headers = File(prefabHeadersDir, "react_codegen_rncore").absolutePath
}
create("react_debug") { headers = File(prefabHeadersDir, "react_debug").absolutePath }
create("react_utils") { headers = File(prefabHeadersDir, "react_utils").absolutePath }
create("react_render_componentregistry") {
headers = File(prefabHeadersDir, "react_render_componentregistry").absolutePath
}
create("react_newarchdefaults") {
headers = File(prefabHeadersDir, "react_newarchdefaults").absolutePath
}
create("react_cxxreactpackage") {
headers = File(prefabHeadersDir, "react_cxxreactpackage").absolutePath
}
create("react_render_animations") {
headers = File(prefabHeadersDir, "react_render_animations").absolutePath
}
create("react_render_core") {
headers = File(prefabHeadersDir, "react_render_core").absolutePath
}
create("react_render_graphics") {
headers = File(prefabHeadersDir, "react_render_graphics").absolutePath
}
create("rrc_image") { headers = File(prefabHeadersDir, "rrc_image").absolutePath }
create("rrc_root") { headers = File(prefabHeadersDir, "rrc_root").absolutePath }
create("rrc_view") { headers = File(prefabHeadersDir, "rrc_view").absolutePath }
create("rrc_text") { headers = File(prefabHeadersDir, "rrc_text").absolutePath }
create("rrc_textinput") { headers = File(prefabHeadersDir, "rrc_textinput").absolutePath }
create("rrc_legacyviewmanagerinterop") {
headers = File(prefabHeadersDir, "rrc_legacyviewmanagerinterop").absolutePath
}
create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath }
create("glog") { headers = File(prefabHeadersDir, "glog").absolutePath }
create("fabricjni") { headers = File(prefabHeadersDir, "fabricjni").absolutePath }
create("react_render_mapbuffer") {
headers = File(prefabHeadersDir, "react_render_mapbuffer").absolutePath
}
create("react_render_textlayoutmanager") {
headers = File(prefabHeadersDir, "react_render_textlayoutmanager").absolutePath
}
create("yoga") { headers = File(prefabHeadersDir, "yoga").absolutePath }
create("folly_runtime") { headers = File(prefabHeadersDir, "folly_runtime").absolutePath }
create("react_nativemodule_core") {
headers = File(prefabHeadersDir, "react_nativemodule_core").absolutePath
}
create("react_render_imagemanager") {
headers = File(prefabHeadersDir, "react_render_imagemanager").absolutePath
}
create("react_render_uimanager") {
headers = File(prefabHeadersDir, "react_render_uimanager").absolutePath
}
create("react_render_scheduler") {
headers = File(prefabHeadersDir, "react_render_scheduler").absolutePath
}
create("react_render_mounting") {
headers = File(prefabHeadersDir, "react_render_mounting").absolutePath
}
create("reactnativejni") { headers = File(prefabHeadersDir, "reactnativejni").absolutePath }
create("hermes_executor") { headers = File(prefabHeadersDir, "hermes_executor").absolutePath }
create("jscexecutor") { headers = File(prefabHeadersDir, "jscexecutor").absolutePath }
create("jsinspector") { headers = File(prefabHeadersDir, "jsinspector").absolutePath }
}
publishing {
multipleVariants {
withSourcesJar()
includeBuildTypeValues("debug", "release")
}
}
testOptions {
unitTests { isIncludeAndroidResources = true }
targetSdk = libs.versions.targetSdk.get().toInt()
}
}
dependencies {
api(libs.androidx.appcompat)
api(libs.androidx.appcompat.resources)
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.fbjni)
api(libs.fresco)
api(libs.fresco.middleware)
api(libs.fresco.imagepipeline.okhttp3)
api(libs.fresco.ui.common)
api(libs.infer.annotation)
api(libs.soloader)
api(libs.yoga.proguard.annotations)
api(libs.jsr305)
api(libs.okhttp3.urlconnection)
api(libs.okhttp3)
api(libs.okio)
compileOnly(libs.javax.annotation.api)
api(libs.javax.inject)
// It's up to the consumer to decide if hermes should be included or not.
// Therefore hermes-engine is a compileOnly dependency.
compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine"))
testImplementation(libs.junit)
testImplementation(libs.assertj)
testImplementation(libs.mockito)
testImplementation(libs.robolectric)
testImplementation(libs.thoughtworks)
}
react {
// TODO: The library name is chosen for parity with Fabric components & iOS
// This should be changed to a more generic name, e.g. `ReactCoreSpec`.
libraryName = "rncore"
jsRootDir = file("../src")
}
// For build from source, we need to override the privateReact extension.
// This is needed as the build-from-source won't have a com.android.application
// module to apply the plugin to, so it's codegenDir and reactNativeDir won't be evaluated.
if (rootProject.name == "react-native-build-from-source") {
rootProject.extensions.getByType(PrivateReactExtension::class.java).apply {
codegenDir = file("$rootDir/../@react-native/codegen")
reactNativeDir = file("$rootDir")
}
}
kotlin {
jvmToolchain(17)
explicitApi()
}
/* Publishing Configuration */
apply(from = "./publish.gradle")
// We need to override the artifact ID as this project is called `ReactAndroid` but
// the maven coordinates are on `react-android`.
// Please note that the original coordinates, `react-native`, have been voided
// as they caused https://github.com/facebook/react-native/issues/35210
publishing {
publications { getByName("release", MavenPublication::class) { artifactId = "react-android" } }
}

View File

@@ -0,0 +1,154 @@
# 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.
# This CMake file takes care of creating everything you need to build and link
# your C++ source code in a React Native Application for Android.
# You just need to call `project(<my_project_name>)` and import this file.
# Specifically this file will:
# - Take care of creating a shared library called as your project
# - Take care of setting the correct compile options
# - Include all the pre-built libraries in your build graph
# - Link your library against those prebuilt libraries so you can access JSI, Fabric, etc.
# - Link your library against any autolinked library.
# - Make sure ccache is used as part of the compilation process, if you have it installed.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${CMAKE_CURRENT_LIST_DIR}/folly-flags.cmake)
# We configured the REACT_COMMON_DIR variable as it's commonly used to reference
# shared C++ code in other targets.
set(REACT_COMMON_DIR ${REACT_ANDROID_DIR}/../ReactCommon)
# If you have ccache installed, we're going to honor it.
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)
set(BUILD_DIR ${PROJECT_BUILD_DIR})
if(CMAKE_HOST_WIN32)
string(REPLACE "\\" "/" BUILD_DIR ${BUILD_DIR})
endif()
file(GLOB input_SRC CONFIGURE_DEPENDS
*.cpp
${BUILD_DIR}/generated/rncli/src/main/jni/*.cpp)
add_library(${CMAKE_PROJECT_NAME} SHARED ${input_SRC})
target_include_directories(${CMAKE_PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni)
target_compile_options(${CMAKE_PROJECT_NAME}
PRIVATE
-Wall
-Werror
# We suppress cpp #error and #warning to don't fail the build
# due to use migrating away from
# #include <react/renderer/graphics/conversions.h>
# This can be removed for React Native 0.73
-Wno-error=cpp
-fexceptions
-frtti
-std=c++20
-DLOG_TAG=\"ReactNative\")
# Prefab packages from React Native
find_package(ReactAndroid REQUIRED CONFIG)
add_library(react_render_debug ALIAS ReactAndroid::react_render_debug)
add_library(turbomodulejsijni ALIAS ReactAndroid::turbomodulejsijni)
add_library(runtimeexecutor ALIAS ReactAndroid::runtimeexecutor)
add_library(react_codegen_rncore ALIAS ReactAndroid::react_codegen_rncore)
add_library(react_debug ALIAS ReactAndroid::react_debug)
add_library(react_utils ALIAS ReactAndroid::react_utils)
add_library(react_render_componentregistry ALIAS ReactAndroid::react_render_componentregistry)
add_library(react_newarchdefaults ALIAS ReactAndroid::react_newarchdefaults)
add_library(react_cxxreactpackage ALIAS ReactAndroid::react_cxxreactpackage)
add_library(react_render_core ALIAS ReactAndroid::react_render_core)
add_library(react_render_graphics ALIAS ReactAndroid::react_render_graphics)
add_library(rrc_view ALIAS ReactAndroid::rrc_view)
add_library(rrc_text ALIAS ReactAndroid::rrc_text)
add_library(rrc_textinput ALIAS ReactAndroid::rrc_textinput)
add_library(jsi ALIAS ReactAndroid::jsi)
add_library(glog ALIAS ReactAndroid::glog)
add_library(fabricjni ALIAS ReactAndroid::fabricjni)
add_library(react_render_mapbuffer ALIAS ReactAndroid::react_render_mapbuffer)
add_library(react_render_textlayoutmanager ALIAS ReactAndroid::react_render_textlayoutmanager)
add_library(yoga ALIAS ReactAndroid::yoga)
add_library(folly_runtime ALIAS ReactAndroid::folly_runtime)
add_library(react_nativemodule_core ALIAS ReactAndroid::react_nativemodule_core)
add_library(react_render_imagemanager ALIAS ReactAndroid::react_render_imagemanager)
add_library(rrc_image ALIAS ReactAndroid::rrc_image)
add_library(rrc_legacyviewmanagerinterop ALIAS ReactAndroid::rrc_legacyviewmanagerinterop)
find_package(fbjni REQUIRED CONFIG)
add_library(fbjni ALIAS fbjni::fbjni)
target_link_libraries(${CMAKE_PROJECT_NAME}
fabricjni # prefab ready
fbjni # via 3rd party prefab
folly_runtime # prefab ready
glog # prefab ready
jsi # prefab ready
react_codegen_rncore # prefab ready
react_debug # prefab ready
react_utils # prefab ready
react_nativemodule_core # prefab ready
react_newarchdefaults # prefab ready
react_cxxreactpackage # prefab ready
react_render_componentregistry # prefab ready
react_render_core # prefab ready
react_render_debug # prefab ready
react_render_graphics # prefab ready
react_render_imagemanager # prefab ready
react_render_mapbuffer # prefab ready
react_render_textlayoutmanager # prefab ready
rrc_image # prefab ready
rrc_view # prefab ready
rrc_text # prefab ready
rrc_textinput # prefab ready
rrc_legacyviewmanagerinterop # prefab ready
runtimeexecutor # prefab ready
turbomodulejsijni # prefab ready
yoga) # prefab ready
# We use an interface target to propagate flags to all the generated targets
# such as the folly flags or others.
add_library(common_flags INTERFACE)
target_compile_options(common_flags INTERFACE ${folly_FLAGS})
target_link_libraries(ReactAndroid::react_codegen_rncore INTERFACE common_flags)
# If project is on RN CLI v9, then we can use the following lines to link against the autolinked 3rd party libraries.
if(EXISTS ${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni/Android-rncli.cmake)
include(${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni/Android-rncli.cmake)
target_link_libraries(${CMAKE_PROJECT_NAME} ${AUTOLINKED_LIBRARIES})
foreach(autolinked_library ${AUTOLINKED_LIBRARIES})
target_link_libraries(${autolinked_library} common_flags)
endforeach()
endif()
# If project is running codegen at the app level, we want to link and build the generated library.
if(EXISTS ${PROJECT_BUILD_DIR}/generated/source/codegen/jni/CMakeLists.txt)
add_subdirectory(${PROJECT_BUILD_DIR}/generated/source/codegen/jni/ codegen_app_build)
get_property(APP_CODEGEN_TARGET DIRECTORY ${PROJECT_BUILD_DIR}/generated/source/codegen/jni/ PROPERTY BUILDSYSTEM_TARGETS)
target_link_libraries(${CMAKE_PROJECT_NAME} ${APP_CODEGEN_TARGET})
target_link_libraries(${APP_CODEGEN_TARGET} common_flags)
# We need to pass the generated header and module provider to the OnLoad.cpp file so
# local app modules can properly be linked.
string(REGEX REPLACE "react_codegen_" "" APP_CODEGEN_HEADER "${APP_CODEGEN_TARGET}")
target_compile_options(${CMAKE_PROJECT_NAME}
PRIVATE
-DREACT_NATIVE_APP_CODEGEN_HEADER="${APP_CODEGEN_HEADER}.h"
-DREACT_NATIVE_APP_COMPONENT_DESCRIPTORS_HEADER="react/renderer/components/${APP_CODEGEN_HEADER}/ComponentDescriptors.h"
-DREACT_NATIVE_APP_COMPONENT_REGISTRATION=${APP_CODEGEN_HEADER}_registerComponentDescriptorsFromCodegen
-DREACT_NATIVE_APP_MODULE_PROVIDER=${APP_CODEGEN_HEADER}_ModuleProvider
)
endif()

View File

@@ -0,0 +1,31 @@
# 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.
# This CMake file is the default used by apps and is placed inside react-native
# to encapsulate it from user space (so you won't need to touch C++/Cmake code at all on Android).
#
# If you wish to customize it (because you want to manually link a C++ library or pass a custom
# compilation flag) you can:
#
# 1. Copy this CMake file inside the `android/app/src/main/jni` folder of your project
# 2. Copy the OnLoad.cpp (in this same folder) file inside the same folder as above.
# 3. Extend your `android/app/build.gradle` as follows
#
# android {
# // Other config here...
# externalNativeBuild {
# cmake {
# path "src/main/jni/CMakeLists.txt"
# }
# }
# }
cmake_minimum_required(VERSION 3.13)
# Define the library name here.
project(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)

View File

@@ -0,0 +1,114 @@
/*
* 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.
*/
// This C++ file is part of the default configuration used by apps and is placed
// inside react-native to encapsulate it from user space (so you won't need to
// touch C++/Cmake code at all on Android).
//
// If you wish to customize it (because you want to manually link a C++ library
// or pass a custom compilation flag) you can:
//
// 1. Copy this CMake file inside the `android/app/src/main/jni` folder of your
// project
// 2. Copy the OnLoad.cpp (in this same folder) file inside the same folder as
// above.
// 3. Extend your `android/app/build.gradle` as follows
//
// android {
// // Other config here...
// externalNativeBuild {
// cmake {
// path "src/main/jni/CMakeLists.txt"
// }
// }
// }
#include <DefaultComponentsRegistry.h>
#include <DefaultTurboModuleManagerDelegate.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncli.h>
#ifdef REACT_NATIVE_APP_CODEGEN_HEADER
#include REACT_NATIVE_APP_CODEGEN_HEADER
#endif
#ifdef REACT_NATIVE_APP_COMPONENT_DESCRIPTORS_HEADER
#include REACT_NATIVE_APP_COMPONENT_DESCRIPTORS_HEADER
#endif
namespace facebook::react {
void registerComponents(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
// Custom Fabric Components go here. You can register custom
// components coming from your App or from 3rd party libraries here.
//
// providerRegistry->add(concreteComponentDescriptorProvider<
// MyComponentDescriptor>());
// We link app local components if available
#ifdef REACT_NATIVE_APP_COMPONENT_REGISTRATION
REACT_NATIVE_APP_COMPONENT_REGISTRATION(registry);
#endif
// And we fallback to the components autolinked by RN CLI
rncli_registerProviders(registry);
}
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
// Here you can provide your CXX Turbo Modules coming from
// either your application or from external libraries. The approach to follow
// is similar to the following (for a module called `NativeCxxModuleExample`):
//
// if (name == NativeCxxModuleExample::kModuleName) {
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
// }
// And we fallback to the CXX module providers autolinked by RN CLI
return rncli_cxxModuleProvider(name, jsInvoker);
}
std::shared_ptr<TurboModule> javaModuleProvider(
const std::string& name,
const JavaTurboModule::InitParams& params) {
// 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(name, params);
// if (module != nullptr) {
// return module;
// }
// return rncore_ModuleProvider(name, params);
// We link app local modules if available
#ifdef REACT_NATIVE_APP_MODULE_PROVIDER
auto module = REACT_NATIVE_APP_MODULE_PROVIDER(name, params);
if (module != nullptr) {
return module;
}
#endif
// And we fallback to the module providers autolinked by RN CLI
return rncli_ModuleProvider(name, params);
}
} // namespace facebook::react
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
return facebook::jni::initialize(vm, [] {
facebook::react::DefaultTurboModuleManagerDelegate::cxxModuleProvider =
&facebook::react::cxxModuleProvider;
facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider =
&facebook::react::javaModuleProvider;
facebook::react::DefaultComponentsRegistry::
registerComponentDescriptorsFromEntryPoint =
&facebook::react::registerComponents;
});
}

View File

@@ -0,0 +1,23 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
# This CMake file exposes the Folly Flags that all the libraries should use when
# compiling/linking against a dependency which requires folly.
SET(folly_FLAGS
-DFOLLY_NO_CONFIG=1
-DFOLLY_HAVE_CLOCK_GETTIME=1
-DFOLLY_USE_LIBCPP=1
-DFOLLY_CFG_NO_COROUTINES=1
-DFOLLY_MOBILE=1
-DFOLLY_HAVE_RECVMMSG=1
-DFOLLY_HAVE_PTHREAD=1
# Once we target android-23 above, we can comment
# the following line. NDK uses GNU style stderror_r() after API 23.
-DFOLLY_HAVE_XSI_STRERROR_R=1
)

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
import org.jetbrains.kotlin.gradle.plugin.extraProperties
plugins { id("maven-publish") }
group = "com.facebook.react"
version =
parent?.extraProperties?.get("publishing_version")
?: error("publishing_version not set for external-artifacts")
configurations.maybeCreate("default")
// Those artifacts should be placed inside the `artifacts/hermes-ios-*.tar.gz` location.
val hermesiOSDebugArtifactFile: RegularFile =
layout.projectDirectory.file("artifacts/hermes-ios-debug.tar.gz")
val hermesiOSDebugArtifact: PublishArtifact =
artifacts.add("default", hermesiOSDebugArtifactFile) {
type = "tgz"
extension = "tar.gz"
classifier = "hermes-ios-debug"
}
val hermesiOSReleaseArtifactFile: RegularFile =
layout.projectDirectory.file("artifacts/hermes-ios-release.tar.gz")
val hermesiOSReleaseArtifact: PublishArtifact =
artifacts.add("default", hermesiOSReleaseArtifactFile) {
type = "tgz"
extension = "tar.gz"
classifier = "hermes-ios-release"
}
// Those artifacts should be placed inside the `artifacts/hermes-*.framework.dSYM` location
val hermesDSYMDebugArtifactFile: RegularFile =
layout.projectDirectory.file("artifacts/hermes-framework-dSYM-debug.tar.gz")
val hermesDSYMDebugArtifact: PublishArtifact =
artifacts.add("default", hermesDSYMDebugArtifactFile) {
type = "tgz"
extension = "tar.gz"
classifier = "hermes-framework-dSYM-debug"
}
val hermesDSYMReleaseArtifactFile: RegularFile =
layout.projectDirectory.file("artifacts/hermes-framework-dSYM-release.tar.gz")
val hermesDSYMReleaseArtifact: PublishArtifact =
artifacts.add("default", hermesDSYMReleaseArtifactFile) {
type = "tgz"
extension = "tar.gz"
classifier = "hermes-framework-dSYM-release"
}
apply(from = "../publish.gradle")
publishing {
publications {
getByName("release", MavenPublication::class) {
artifactId = "react-native-artifacts"
artifact(hermesiOSDebugArtifact)
artifact(hermesiOSReleaseArtifact)
artifact(hermesDSYMDebugArtifact)
artifact(hermesDSYMReleaseArtifact)
}
}
}

View File

@@ -0,0 +1,29 @@
VERSION_NAME=0.74.0
react.internal.publishingGroup=com.facebook.react
android.useAndroidX=true
android.enableJetifier=true
# We want to have more fine grained control on the Java version for
# ReactAndroid, therefore we disable RGNP Java version alignment mechanism
react.internal.disableJavaVersionAlignment=true
# Binary Compatibility Validator properties
binaryCompatibilityValidator.ignoredClasses=com.facebook.react.BuildConfig
binaryCompatibilityValidator.ignoredPackages=com.facebook.debug,\
com.facebook.fbreact,\
com.facebook.hermes,\
com.facebook.perftest,\
com.facebook.proguard,\
com.facebook.react.bridgeless.internal,\
com.facebook.react.flipper,\
com.facebook.react.internal,\
com.facebook.react.module.processing,\
com.facebook.react.processing,\
com.facebook.react.views.text.internal,\
com.facebook.systrace,\
com.facebook.yoga
binaryCompatibilityValidator.nonPublicMarkers=com.facebook.react.common.annotations.VisibleForTesting,\
com.facebook.react.common.annotations.UnstableReactNativeAPI
binaryCompatibilityValidator.validationDisabled=true
binaryCompatibilityValidator.outputApiFileName=ReactAndroid

View File

@@ -0,0 +1,320 @@
/*
* 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.
*/
import de.undercouch.gradle.tasks.download.Download
import org.apache.tools.ant.taskdefs.condition.Os
plugins {
id("maven-publish")
id("signing")
alias(libs.plugins.android.library)
alias(libs.plugins.download)
}
group = "com.facebook.react"
version = parent?.properties?.get("publishing_version")?.toString()!!
val cmakeVersion = parent?.properties?.get("cmake_version")?.toString()!!
/**
* We use the bundled version of CMake in the Android SDK if available, to don't force Android users
* to install CMake externally.
*/
fun findCmakePath(cmakeVersion: String): String {
val cmakeRelativePath = "cmake/${cmakeVersion}/bin/cmake"
if (System.getenv("ANDROID_SDK_ROOT") != null &&
File("${System.getenv("ANDROID_SDK_ROOT")}/${cmakeRelativePath}").exists()) {
return "${System.getenv("ANDROID_SDK_ROOT")}/${cmakeRelativePath}"
}
if (System.getenv("ANDROID_HOME") != null &&
File("${System.getenv("ANDROID_HOME")}/${cmakeRelativePath}").exists()) {
return "${System.getenv("ANDROID_HOME")}/${cmakeRelativePath}"
}
return "cmake"
}
val reactNativeRootDir = project(":packages:react-native:ReactAndroid").projectDir.parent
val customDownloadDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
val downloadsDir =
if (customDownloadDir != null) {
File(customDownloadDir)
} else {
File(reactNativeRootDir, "sdks/download")
}
// By default we are going to download and unzip hermes inside the /sdks/hermes folder
// but you can provide an override for where the hermes source code is located.
val buildDir = project.layout.buildDirectory.get().asFile
val overrideHermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") != null
val hermesDir =
if (overrideHermesDir) {
File(System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR"))
} else {
File(reactNativeRootDir, "sdks/hermes")
}
val hermesBuildDir = File("$buildDir/hermes")
val hermesCOutputBinary = File("$buildDir/hermes/bin/hermesc")
// This filetree represents the file of the Hermes build that we want as input/output
// of the buildHermesC task. Gradle will compute the hash of files in the file tree
// and won't rebuilt hermesc unless those files are changing.
val hermesBuildOutputFileTree =
fileTree(hermesBuildDir.toString())
.include(
"**/*.make",
"**/*.cmake",
"**/*.marks",
"**/compiler_depends.ts",
"**/Makefile",
"**/link.txt")
var hermesVersion = "main"
val hermesVersionFile = File(reactNativeRootDir, "sdks/.hermesversion")
if (hermesVersionFile.exists()) {
hermesVersion = hermesVersionFile.readText()
}
val ndkBuildJobs = Runtime.getRuntime().availableProcessors().toString()
val prefabHeadersDir = File("$buildDir/prefab-headers")
// We inject the JSI directory used inside the Hermes build with the -DJSI_DIR config.
val jsiDir = File(reactNativeRootDir, "ReactCommon/jsi")
val downloadHermes by
tasks.creating(Download::class) {
src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
onlyIfModified(true)
overwrite(true)
useETag("all")
retries(5)
dest(File(downloadsDir, "hermes.tar.gz"))
}
val unzipHermes by
tasks.registering(Copy::class) {
dependsOn(downloadHermes)
from(tarTree(downloadHermes.dest)) {
eachFile {
// We flatten the unzip as the tarball contains a `facebook-hermes-<SHA>`
// folder at the top level.
if (this.path.startsWith("facebook-hermes-")) {
this.path = this.path.substringAfter("/")
}
}
}
into(hermesDir)
}
val configureBuildForHermes by
tasks.registering(Exec::class) {
workingDir(hermesDir)
inputs.dir(hermesDir)
outputs.files(hermesBuildOutputFileTree)
commandLine(
windowsAwareCommandLine(
findCmakePath(cmakeVersion),
if (Os.isFamily(Os.FAMILY_WINDOWS)) "-GNMake Makefiles" else "",
"-S",
".",
"-B",
hermesBuildDir.toString(),
"-DJSI_DIR=" + jsiDir.absolutePath))
}
val buildHermesC by
tasks.registering(Exec::class) {
dependsOn(configureBuildForHermes)
workingDir(hermesDir)
inputs.files(hermesBuildOutputFileTree)
outputs.file(hermesCOutputBinary)
commandLine(
windowsAwareCommandLine(
findCmakePath(cmakeVersion),
"--build",
hermesBuildDir.toString(),
"--target",
"hermesc",
"-j",
ndkBuildJobs,
))
}
val prepareHeadersForPrefab by
tasks.registering(Copy::class) {
dependsOn(buildHermesC)
from("$hermesDir/API")
from("$hermesDir/public")
include("**/*.h")
exclude("jsi/**")
into(prefabHeadersDir)
}
fun windowsAwareCommandLine(vararg commands: String): List<String> {
val result =
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
mutableListOf("cmd", "/c")
} else {
mutableListOf()
}
result.addAll(commands)
return result
}
fun reactNativeArchitectures(): List<String> {
val value = project.properties["reactNativeArchitectures"]
return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
}
repositories {
// Normally RNGP will set repositories for all modules,
// but when consumed from source, we need to re-declare
// those repositories as there is no app module there.
mavenCentral()
google()
}
android {
compileSdk = libs.versions.compileSdk.get().toInt()
buildToolsVersion = libs.versions.buildTools.get()
namespace = "com.facebook.hermes"
// Used to override the NDK path/version on internal CI or by allowing
// users to customize the NDK path/version from their root project (e.g. for Apple Silicon
// support)
if (rootProject.hasProperty("ndkPath") && rootProject.properties["ndkPath"] != null) {
ndkPath = rootProject.properties["ndkPath"].toString()
}
if (rootProject.hasProperty("ndkVersion") && rootProject.properties["ndkVersion"] != null) {
ndkVersion = rootProject.properties["ndkVersion"].toString()
} else {
ndkVersion = libs.versions.ndkVersion.get()
}
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
externalNativeBuild {
cmake {
arguments(
"-DHERMES_IS_ANDROID=True",
"-DANDROID_STL=c++_shared",
"-DANDROID_PIE=True",
"-DIMPORT_HERMESC=${File(hermesBuildDir, "ImportHermesc.cmake").toString()}",
"-DJSI_DIR=${jsiDir}",
"-DHERMES_SLOW_DEBUG=False",
"-DHERMES_BUILD_SHARED_JSI=True",
"-DHERMES_RELEASE_VERSION=for RN ${version}",
// We intentionally build Hermes with Intl support only. This is to simplify
// the build setup and to avoid overcomplicating the build-type matrix.
"-DHERMES_ENABLE_INTL=True",
// Due to https://github.com/android/ndk/issues/1693 we're losing Android
// specific compilation flags. This can be removed once we moved to NDK 25/26
"-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON")
targets("libhermes")
}
}
ndk { abiFilters.addAll(reactNativeArchitectures()) }
}
externalNativeBuild {
cmake {
version = cmakeVersion
path = File("$hermesDir/CMakeLists.txt")
}
}
buildTypes {
debug {
externalNativeBuild {
cmake {
// JS developers aren't VM developers.
// Therefore we're passing as build type Release, to provide a faster build.
// This has the (unlucky) side effect of letting AGP call the build
// tasks `configureCMakeRelease` while is actually building the debug flavor.
arguments("-DCMAKE_BUILD_TYPE=Release")
// Adding -O3 to handle the issue here:
// https://github.com/android/ndk/issues/1740#issuecomment-1198438260
// The old NDK toolchain is not passing -O3 correctly for release CMake builds. This is
// fixed in NDK 25 and can be removed once we're there.
cppFlags("-O3")
}
}
}
release {
externalNativeBuild {
cmake {
arguments(
"-DCMAKE_BUILD_TYPE=MinSizeRel",
// For release builds, we don't want to enable the Hermes Debugger.
"-DHERMES_ENABLE_DEBUGGER=False")
}
}
}
}
sourceSets.getByName("main") {
manifest.srcFile("$hermesDir/android/hermes/src/main/AndroidManifest.xml")
java.srcDir("$hermesDir/lib/Platform/Intl/java")
}
buildFeatures {
prefab = true
prefabPublishing = true
}
dependencies {
implementation(libs.fbjni)
implementation(libs.soloader)
implementation(libs.yoga.proguard.annotations)
implementation(libs.androidx.annotation)
}
packaging {
jniLibs.excludes.add("**/libc++_shared.so")
jniLibs.excludes.add("**/libjsi.so")
jniLibs.excludes.add("**/libfbjni.so")
}
publishing {
multipleVariants {
withSourcesJar()
allVariants()
}
}
prefab {
create("libhermes") {
headers = prefabHeadersDir.absolutePath
libraryName = "libhermes"
}
}
}
afterEvaluate {
if (!overrideHermesDir) {
// If you're not specifying a Hermes Path override, we want to
// download/unzip Hermes from Github then.
tasks.getByName("configureBuildForHermes").dependsOn(unzipHermes)
tasks.getByName("prepareHeadersForPrefab").dependsOn(unzipHermes)
}
tasks.getByName("preBuild").dependsOn(buildHermesC)
tasks.getByName("preBuild").dependsOn(prepareHeadersForPrefab)
}
/* Publishing Configuration */
apply(from = "../publish.gradle")
// We need to override the artifact ID as this project is called `hermes-engine` but
// the maven coordinates are on `hermes-android`.
// Please note that the original coordinates, `hermes-engine`, have been voided
// as they caused https://github.com/facebook/react-native/issues/35210
publishing {
publications { getByName("release", MavenPublication::class) { artifactId = "hermes-android" } }
}

View File

@@ -0,0 +1 @@
android.disableAutomaticComponentCreation=true

View File

@@ -0,0 +1,76 @@
# 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.
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
# -dontobfuscate
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
}
-keep @com.facebook.proguard.annotations.DoNotStripAny class * {
*;
}
-keep @com.facebook.jni.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.jni.annotations.DoNotStrip *;
}
-keep @com.facebook.jni.annotations.DoNotStripAny class * {
*;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * implements com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * implements com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
-keep,includedescriptorclasses class com.facebook.react.turbomodule.core.** { *; }
-keep,includedescriptorclasses class com.facebook.react.internal.turbomodule.core.** { *; }
# hermes
-keep class com.facebook.jni.** { *; }
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# yoga
-keep,allowobfuscation @interface com.facebook.yoga.annotations.DoNotStrip
-keep @com.facebook.yoga.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.yoga.annotations.DoNotStrip *;
}

View File

@@ -0,0 +1,82 @@
/*
* 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.
*/
apply plugin: 'maven-publish'
apply plugin: 'signing'
def isSnapshot = findProperty("isSnapshot")?.toBoolean()
def signingKey = findProperty("SIGNING_KEY")
def signingPwd = findProperty("SIGNING_PWD")
def reactAndroidProjectDir = project(':packages:react-native:ReactAndroid').projectDir
def mavenTempLocalUrl = "file:///tmp/maven-local"
publishing {
publications {
release(MavenPublication) {
afterEvaluate {
// We do a multi variant release, so for Android libraries
// we publish `components.release`
if (plugins.hasPlugin("com.android.library")) {
from components.default
}
}
// We populate the publishing version using the project version,
// appending -SNAPSHOT if on nightly or prerelase.
if (isSnapshot) {
version = this.version + "-SNAPSHOT"
} else {
version = this.version
}
pom {
name = "react-native"
description = "A framework for building native apps with React"
url = "https://github.com/facebook/react-native"
developers {
developer {
id = "facebook"
name = "Facebook"
}
}
licenses {
license {
name = "MIT License"
url = "https://github.com/facebook/react-native/blob/HEAD/LICENSE"
distribution = "repo"
}
}
scm {
url = "https://github.com/facebook/react-native.git"
connection = "scm:git:https://github.com/facebook/react-native.git"
developerConnection = "scm:git:git@github.com:facebook/react-native.git"
}
}
}
}
repositories {
maven {
name = "mavenTempLocal"
url = mavenTempLocalUrl
}
}
if (signingKey && signingPwd) {
logger.info("PGP Key found - Signing enabled")
signing {
useInMemoryPgpKeys(signingKey, signingPwd)
sign(publishing.publications.release)
}
} else {
logger.info("Signing disabled as the PGP key was not found")
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!--
This manifest file is used only by Gradle to configure debug-only capabilities
for React Native Apps.
-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
>
<!-- This is necessary to inform the linter about our min API version. The linter walk the tree
up from the file to lint until it find an AndroidManifest with a minSdkVersion. This is then used
as the min SDK to lint the file.-->
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34"
/>
</manifest>

View File

@@ -0,0 +1,24 @@
/*
* 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.annotationprocessors.common;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
public abstract class ProcessorBase extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return processImpl(annotations, roundEnv);
}
protected abstract boolean processImpl(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
}

View File

@@ -0,0 +1,30 @@
/*
* 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.debug.debugoverlay.model;
import javax.annotation.concurrent.Immutable;
/** Tag for a debug overlay log message. Name must be unique. */
@Immutable
public class DebugOverlayTag {
/** Name of tag. */
public final String name;
/** Description to display in settings. */
public final String description;
/** Color for tag display. */
public final int color;
public DebugOverlayTag(String name, String description, int color) {
this.name = name;
this.description = description;
this.color = color;
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.debug.holder;
import com.facebook.debug.debugoverlay.model.DebugOverlayTag;
/** No-op implementation of {@link Printer}. */
public class NoopPrinter implements Printer {
public static final NoopPrinter INSTANCE = new NoopPrinter();
private NoopPrinter() {}
@Override
public void logMessage(DebugOverlayTag tag, String message, Object... args) {}
@Override
public void logMessage(DebugOverlayTag tag, String message) {}
@Override
public boolean shouldDisplayLogMessage(final DebugOverlayTag tag) {
return false;
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.debug.holder;
import com.facebook.debug.debugoverlay.model.DebugOverlayTag;
/** Interface to debugging tool. */
public interface Printer {
void logMessage(final DebugOverlayTag tag, final String message, Object... args);
void logMessage(final DebugOverlayTag tag, final String message);
boolean shouldDisplayLogMessage(final DebugOverlayTag tag);
}

View File

@@ -0,0 +1,26 @@
/*
* 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.debug.holder;
/** Holder for debugging tool instance. */
public class PrinterHolder {
private static Printer sPrinter = NoopPrinter.INSTANCE;
public static void setPrinter(Printer printer) {
if (printer == null) {
sPrinter = NoopPrinter.INSTANCE;
} else {
sPrinter = printer;
}
}
public static Printer getPrinter() {
return sPrinter;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.debug.tags;
import android.graphics.Color;
import com.facebook.debug.debugoverlay.model.DebugOverlayTag;
/** Category for debug overlays. */
public class ReactDebugOverlayTags {
public static final DebugOverlayTag PERFORMANCE =
new DebugOverlayTag("Performance", "Markers for Performance", Color.GREEN);
public static final DebugOverlayTag NAVIGATION =
new DebugOverlayTag("Navigation", "Tag for navigation", Color.rgb(0x9C, 0x27, 0xB0));
public static final DebugOverlayTag RN_CORE =
new DebugOverlayTag("RN Core", "Tag for React Native Core", Color.BLACK);
public static final DebugOverlayTag BRIDGE_CALLS =
new DebugOverlayTag(
"Bridge Calls", "JS to Java calls (warning: this is spammy)", Color.MAGENTA);
public static final DebugOverlayTag NATIVE_MODULE =
new DebugOverlayTag("Native Module", "Native Module init", Color.rgb(0x80, 0x00, 0x80));
public static final DebugOverlayTag UI_MANAGER =
new DebugOverlayTag(
"UI Manager",
"UI Manager View Operations (requires restart\nwarning: this is spammy)",
Color.CYAN);
public static final DebugOverlayTag FABRIC_UI_MANAGER =
new DebugOverlayTag("FabricUIManager", "Fabric UI Manager View Operations", Color.CYAN);
public static final DebugOverlayTag FABRIC_RECONCILER =
new DebugOverlayTag("FabricReconciler", "Reconciler for Fabric", Color.CYAN);
public static final DebugOverlayTag RELAY =
new DebugOverlayTag("Relay", "including prefetching", Color.rgb(0xFF, 0x99, 0x00));
}

View File

@@ -0,0 +1,18 @@
/*
* 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.hermes.instrumentation;
public interface HermesMemoryDumper {
boolean shouldSaveSnapshot();
String getInternalStorage();
String getId();
void setMetaData(String crashId);
}

View File

@@ -0,0 +1,32 @@
/*
* 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.hermes.instrumentation;
import com.facebook.soloader.SoLoader;
/** Hermes sampling profiler static JSI API. */
public class HermesSamplingProfiler {
static {
SoLoader.loadLibrary("jsijniprofiler");
}
/** Start sample profiling. */
public static native void enable();
/** Stop sample profiling. */
public static native void disable();
/**
* Dump sampled stack traces to file.
*
* @param filename the file to dump sampling trace to.
*/
public static native void dumpSampledTraceToFile(String filename);
private HermesSamplingProfiler() {}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.hermes.reactexecutor;
import com.facebook.jni.HybridData;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.soloader.SoLoader;
import javax.annotation.Nullable;
public class HermesExecutor extends JavaScriptExecutor {
private static String mode_;
static {
loadLibrary();
}
public static void loadLibrary() throws UnsatisfiedLinkError {
if (mode_ == null) {
// libhermes must be loaded explicitly to invoke its JNI_OnLoad.
SoLoader.loadLibrary("hermes");
SoLoader.loadLibrary("hermes_executor");
// libhermes_executor is built differently for Debug & Release so we load the proper mode.
mode_ = ReactBuildConfig.DEBUG ? "Debug" : "Release";
}
}
HermesExecutor(@Nullable RuntimeConfig config, boolean enableDebugger, String debuggerName) {
super(
config == null
? initHybridDefaultConfig(enableDebugger, debuggerName)
: initHybrid(enableDebugger, debuggerName, config.heapSizeMB));
}
@Override
public String getName() {
return "HermesExecutor" + mode_;
}
private static native HybridData initHybridDefaultConfig(
boolean enableDebugger, String debuggerName);
private static native HybridData initHybrid(
boolean enableDebugger, String debuggerName, long heapSizeMB);
}

View File

@@ -0,0 +1,57 @@
/*
* 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.hermes.reactexecutor;
import com.facebook.hermes.instrumentation.HermesSamplingProfiler;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
public class HermesExecutorFactory implements JavaScriptExecutorFactory {
private static final String TAG = "Hermes";
private final RuntimeConfig mConfig;
private boolean mEnableDebugger = true;
private String mDebuggerName = "";
public HermesExecutorFactory() {
this(null);
}
public HermesExecutorFactory(RuntimeConfig config) {
mConfig = config;
}
public void setEnableDebugger(boolean enableDebugger) {
mEnableDebugger = enableDebugger;
}
public void setDebuggerName(String debuggerName) {
mDebuggerName = debuggerName;
}
@Override
public JavaScriptExecutor create() {
return new HermesExecutor(mConfig, mEnableDebugger, mDebuggerName);
}
@Override
public void startSamplingProfiler() {
HermesSamplingProfiler.enable();
}
@Override
public void stopSamplingProfiler(String filename) {
HermesSamplingProfiler.dumpSampledTraceToFile(filename);
HermesSamplingProfiler.disable();
}
@Override
public String toString() {
return "JSIExecutor+HermesRuntime";
}
}

View File

@@ -0,0 +1,13 @@
/*
* 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.hermes.reactexecutor;
/** Holds runtime configuration for a Hermes VM instance (master or snapshot). */
public final class RuntimeConfig {
public long heapSizeMB;
}

View File

@@ -0,0 +1,16 @@
# 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.
# For common use cases for the hybrid pattern, keep symbols which may
# be referenced only from C++.
-keepclassmembers class * {
com.facebook.jni.HybridData *;
<init>(com.facebook.jni.HybridData);
}
-keepclasseswithmembers class * {
com.facebook.jni.HybridData *;
}

View File

@@ -0,0 +1,82 @@
/*
* 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.hermes.unicode;
import com.facebook.proguard.annotations.DoNotStrip;
import java.text.Collator;
import java.text.DateFormat;
import java.text.Normalizer;
import java.util.Locale;
// TODO: use com.facebook.common.locale.Locales.getApplicationLocale() as the current locale,
// rather than the device locale. This is challenging because getApplicationLocale() is only
// available via DI.
@DoNotStrip
public class AndroidUnicodeUtils {
@DoNotStrip
public static int localeCompare(String left, String right) {
Collator collator = Collator.getInstance();
return collator.compare(left, right);
}
@DoNotStrip
public static String dateFormat(double unixtimeMs, boolean formatDate, boolean formatTime) {
DateFormat format;
if (formatDate && formatTime) {
format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
} else if (formatDate) {
format = DateFormat.getDateInstance(DateFormat.MEDIUM);
} else if (formatTime) {
format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
} else {
throw new RuntimeException("Bad dateFormat configuration");
}
return format.format((long) unixtimeMs).toString();
}
@DoNotStrip
public static String convertToCase(String input, int targetCase, boolean useCurrentLocale) {
// These values must match CaseConversion in PlatformUnicode.h
final int targetUppercase = 0;
final int targetLowercase = 1;
// Note Java's case conversions use the user's locale. For example "I".toLowerCase()
// will produce a dotless i. From Java's docs: "To obtain correct results for locale
// insensitive strings, use toLowerCase(Locale.ENGLISH)."
Locale locale = useCurrentLocale ? Locale.getDefault() : Locale.ENGLISH;
switch (targetCase) {
case targetLowercase:
return input.toLowerCase(locale);
case targetUppercase:
return input.toUpperCase(locale);
default:
throw new RuntimeException("Invalid target case");
}
}
@DoNotStrip
public static String normalize(String input, int form) {
// Values must match NormalizationForm in PlatformUnicode.h.
final int formC = 0;
final int formD = 1;
final int formKC = 2;
final int formKD = 3;
switch (form) {
case formC:
return Normalizer.normalize(input, Normalizer.Form.NFC);
case formD:
return Normalizer.normalize(input, Normalizer.Form.NFD);
case formKC:
return Normalizer.normalize(input, Normalizer.Form.NFKC);
case formKD:
return Normalizer.normalize(input, Normalizer.Form.NFKD);
default:
throw new RuntimeException("Invalid form");
}
}
}

View File

@@ -0,0 +1,16 @@
/*
* 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.perftest;
/** PerfTestConfig stub. */
public class PerfTestConfig {
public boolean isRunningInPerfTest() {
return false;
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.proguard.annotations;
import static java.lang.annotation.RetentionPolicy.CLASS;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Add this annotation to a class, to keep all "void set*(***)" and get* methods.
*
* <p>This is useful for classes that are controlled by animator-like classes that control various
* properties with reflection.
*
* <p><b>NOTE:</b> This is <em>not</em> needed for Views because their getters and setters are
* automatically kept by the default Android SDK ProGuard config.
*/
@Target({ElementType.TYPE})
@Retention(CLASS)
public @interface KeepGettersAndSetters {}

View File

@@ -0,0 +1,21 @@
# 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.
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStripAny
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}

View File

@@ -0,0 +1,159 @@
/*
* 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;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ModuleHolder;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.inject.Provider;
/** Abstract class that supports lazy loading of NativeModules by default. */
public abstract class BaseReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
throw new UnsupportedOperationException(
"createNativeModules method is not supported. Use getModule() method instead.");
}
/**
* The API needed for TurboModules. Given a module name, it returns an instance of {@link
* NativeModule} for the name
*
* @param name name of the Native Module
* @param reactContext {@link ReactApplicationContext} context for this
*/
@Override
public abstract @Nullable NativeModule getModule(
@NonNull String name, @NonNull ReactApplicationContext reactContext);
/**
* This is a temporary method till we implement TurboModules. Once we implement TurboModules, we
* will be able to directly call {@link BaseReactPackage#getModule(String,
* ReactApplicationContext)} This method will be removed when TurboModule implementation is
* complete
*
* @param reactContext
* @return
*/
/** package */
Iterable<ModuleHolder> getNativeModuleIterator(final ReactApplicationContext reactContext) {
final Set<Map.Entry<String, ReactModuleInfo>> entrySet =
getReactModuleInfoProvider().getReactModuleInfos().entrySet();
final Iterator<Map.Entry<String, ReactModuleInfo>> entrySetIterator = entrySet.iterator();
// This should ideally be an IteratorConvertor, but we don't have any internal library for it
return () ->
new Iterator<ModuleHolder>() {
@Nullable Map.Entry<String, ReactModuleInfo> nextEntry = null;
private void findNext() {
while (entrySetIterator.hasNext()) {
Map.Entry<String, ReactModuleInfo> entry = entrySetIterator.next();
ReactModuleInfo reactModuleInfo = entry.getValue();
// This Iterator is used to create the NativeModule registry. The NativeModule
// registry must not have TurboModules. Therefore, if TurboModules are enabled, and
// the current NativeModule is a TurboModule, we need to skip iterating over it.
if (ReactFeatureFlags.useTurboModules && reactModuleInfo.isTurboModule()) {
continue;
}
nextEntry = entry;
return;
}
nextEntry = null;
}
@Override
public boolean hasNext() {
if (nextEntry == null) {
findNext();
}
return nextEntry != null;
}
@Override
public ModuleHolder next() {
if (nextEntry == null) {
findNext();
}
if (nextEntry == null) {
throw new NoSuchElementException("ModuleHolder not found");
}
Map.Entry<String, ReactModuleInfo> entry = nextEntry;
// Advance iterator
findNext();
String name = entry.getKey();
ReactModuleInfo reactModuleInfo = entry.getValue();
return new ModuleHolder(reactModuleInfo, new ModuleHolderProvider(name, reactContext));
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove native modules from the list");
}
};
}
/**
* @param reactContext react application context that can be used to create View Managers.
* @return list of module specs that can create the View Managers.
*/
protected List<ModuleSpec> getViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
List<ModuleSpec> viewManagerModuleSpecs = getViewManagers(reactContext);
if (viewManagerModuleSpecs == null || viewManagerModuleSpecs.isEmpty()) {
return Collections.emptyList();
}
List<ViewManager> viewManagers = new ArrayList<>();
for (ModuleSpec moduleSpec : viewManagerModuleSpecs) {
viewManagers.add((ViewManager) moduleSpec.getProvider().get());
}
return viewManagers;
}
public abstract ReactModuleInfoProvider getReactModuleInfoProvider();
private class ModuleHolderProvider implements Provider<NativeModule> {
private final String mName;
private final ReactApplicationContext mReactContext;
public ModuleHolderProvider(String name, ReactApplicationContext reactContext) {
mName = name;
mReactContext = reactContext;
}
@Override
public @Nullable NativeModule get() {
return getModule(mName, mReactContext);
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
/**
* {@code CompositeReactPackage} allows to create a single package composed of views and modules
* from several other packages.
*
* @deprecated
*/
@Deprecated(
since = "CompositeReactPackage is deprecated and will be deleted, use ReactPackage instead",
forRemoval = true)
public class CompositeReactPackage implements ViewManagerOnDemandReactPackage, ReactPackage {
private final List<ReactPackage> mChildReactPackages = new ArrayList<>();
/**
* The order in which packages are passed matters. It may happen that a NativeModule or a
* ViewManager exists in two or more ReactPackages. In that case the latter will win i.e. the
* latter will overwrite the former. This re-occurrence is detected by comparing a name of a
* module.
*/
public CompositeReactPackage(ReactPackage arg1, ReactPackage arg2, ReactPackage... args) {
mChildReactPackages.add(arg1);
mChildReactPackages.add(arg2);
Collections.addAll(mChildReactPackages, args);
}
/** {@inheritDoc} */
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
// This is for backward compatibility.
final Map<String, NativeModule> moduleMap = new HashMap<>();
for (ReactPackage reactPackage : mChildReactPackages) {
/**
* For now, we eagerly initialize the NativeModules inside TurboReactPackages. Ultimately, we
* should turn CompositeReactPackage into a TurboReactPackage and remove this eager
* initialization.
*
* <p>TODO: T45627020
*/
if (reactPackage instanceof BaseReactPackage) {
BaseReactPackage baseReactPackage = (BaseReactPackage) reactPackage;
ReactModuleInfoProvider moduleInfoProvider = baseReactPackage.getReactModuleInfoProvider();
Map<String, ReactModuleInfo> moduleInfos = moduleInfoProvider.getReactModuleInfos();
for (final String moduleName : moduleInfos.keySet()) {
moduleMap.put(moduleName, baseReactPackage.getModule(moduleName, reactContext));
}
continue;
}
for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
moduleMap.put(nativeModule.getName(), nativeModule);
}
}
return new ArrayList<>(moduleMap.values());
}
/** {@inheritDoc} */
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
final Map<String, ViewManager> viewManagerMap = new HashMap<>();
for (ReactPackage reactPackage : mChildReactPackages) {
for (ViewManager viewManager : reactPackage.createViewManagers(reactContext)) {
viewManagerMap.put(viewManager.getName(), viewManager);
}
}
return new ArrayList<>(viewManagerMap.values());
}
/** {@inheritDoc} */
@Override
public Collection<String> getViewManagerNames(ReactApplicationContext reactContext) {
Set<String> uniqueNames = new HashSet<>();
for (ReactPackage reactPackage : mChildReactPackages) {
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
Collection<String> names =
((ViewManagerOnDemandReactPackage) reactPackage).getViewManagerNames(reactContext);
if (names != null) {
uniqueNames.addAll(names);
}
}
}
return uniqueNames;
}
/** {@inheritDoc} */
@Override
public @Nullable ViewManager createViewManager(
ReactApplicationContext reactContext, String viewManagerName) {
ListIterator<ReactPackage> iterator =
mChildReactPackages.listIterator(mChildReactPackages.size());
while (iterator.hasPrevious()) {
ReactPackage reactPackage = iterator.previous();
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
ViewManager viewManager =
((ViewManagerOnDemandReactPackage) reactPackage)
.createViewManager(reactContext, viewManagerName);
if (viewManager != null) {
return viewManager;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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;
import androidx.annotation.NonNull;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.internal.turbomodule.core.TurboModuleManagerDelegate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Deprecated(
since =
"CompositeReactPackageTurboModuleManagerDelegate is deprecated and will be deleted in the future. Please use ReactPackage interface or BaseReactPackage instead.")
@DoNotStrip
public class CompositeReactPackageTurboModuleManagerDelegate
extends ReactPackageTurboModuleManagerDelegate {
protected native HybridData initHybrid();
private CompositeReactPackageTurboModuleManagerDelegate(
ReactApplicationContext context,
List<ReactPackage> packages,
List<TurboModuleManagerDelegate> delegates) {
super(context, packages);
for (TurboModuleManagerDelegate delegate : delegates) {
addTurboModuleManagerDelegate(delegate);
}
}
private native void addTurboModuleManagerDelegate(TurboModuleManagerDelegate delegates);
public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
private final List<ReactPackageTurboModuleManagerDelegate.Builder> mDelegatesBuilder;
public Builder(@NonNull List<ReactPackageTurboModuleManagerDelegate.Builder> delegatesBuilder) {
mDelegatesBuilder = delegatesBuilder;
}
protected ReactPackageTurboModuleManagerDelegate build(
ReactApplicationContext context, List<ReactPackage> packages) {
List<TurboModuleManagerDelegate> delegates = new ArrayList<>();
for (ReactPackageTurboModuleManagerDelegate.Builder delegatesBuilder : mDelegatesBuilder) {
delegates.add(delegatesBuilder.build(context, Collections.<ReactPackage>emptyList()));
}
return new CompositeReactPackageTurboModuleManagerDelegate(context, packages, delegates);
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* 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;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_END;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_START;
import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_CORE_REACT_PACKAGE_END;
import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_CORE_REACT_PACKAGE_START;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.devsupport.LogBoxModule;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
import com.facebook.react.modules.core.HeadlessJsTaskSupportModule;
import com.facebook.react.modules.core.TimingModule;
import com.facebook.react.modules.debug.DevSettingsModule;
import com.facebook.react.modules.debug.SourceCodeModule;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.modules.systeminfo.AndroidInfoModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerResolver;
import com.facebook.systrace.Systrace;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* This is the basic module to support React Native. The debug modules are now in DebugCorePackage.
*/
@ReactModuleList(
// WARNING: If you modify this list, ensure that the list below in method
// getReactModuleInfoByInitialization is also updated
nativeModules = {
AndroidInfoModule.class,
DeviceEventManagerModule.class,
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
UIManagerModule.class,
})
class CoreModulesPackage extends TurboReactPackage implements ReactPackageLogger {
private final ReactInstanceManager mReactInstanceManager;
private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
private final boolean mLazyViewManagersEnabled;
private final int mMinTimeLeftInFrameForNonBatchedOperationMs;
public CoreModulesPackage(
ReactInstanceManager reactInstanceManager,
DefaultHardwareBackBtnHandler hardwareBackBtnHandler,
boolean lazyViewManagersEnabled,
int minTimeLeftInFrameForNonBatchedOperationMs) {
mReactInstanceManager = reactInstanceManager;
mHardwareBackBtnHandler = hardwareBackBtnHandler;
mLazyViewManagersEnabled = lazyViewManagersEnabled;
mMinTimeLeftInFrameForNonBatchedOperationMs = minTimeLeftInFrameForNonBatchedOperationMs;
}
/**
* This method is overridden, since OSS does not run the annotation processor to generate {@link
* CoreModulesPackage$$ReactModuleInfoProvider} class. Here we check if it exists. If it does not
* exist, we generate one manually in {@link
* CoreModulesPackage#getReactModuleInfoByInitialization()} and return that instead.
*/
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
try {
Class<?> reactModuleInfoProviderClass =
Class.forName("com.facebook.react.CoreModulesPackage$$ReactModuleInfoProvider");
return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance();
} catch (ClassNotFoundException e) {
// In OSS case, the annotation processor does not run. We fall back on creating this byhand
Class<? extends NativeModule>[] moduleList =
new Class[] {
AndroidInfoModule.class,
DeviceEventManagerModule.class,
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
UIManagerModule.class,
};
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
for (Class<? extends NativeModule> moduleClass : moduleList) {
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
reactModuleInfoMap.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
reactModule.needsEagerInit(),
reactModule.isCxxModule(),
ReactModuleInfo.classIsTurboModule(moduleClass)));
}
return () -> reactModuleInfoMap;
} catch (InstantiationException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for CoreModulesPackage$$ReactModuleInfoProvider", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for CoreModulesPackage$$ReactModuleInfoProvider", e);
}
}
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
switch (name) {
case AndroidInfoModule.NAME:
return new AndroidInfoModule(reactContext);
case DeviceEventManagerModule.NAME:
return new DeviceEventManagerModule(reactContext, mHardwareBackBtnHandler);
case DevSettingsModule.NAME:
return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager());
case ExceptionsManagerModule.NAME:
return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager());
case LogBoxModule.NAME:
return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager());
case HeadlessJsTaskSupportModule.NAME:
return new HeadlessJsTaskSupportModule(reactContext);
case SourceCodeModule.NAME:
return new SourceCodeModule(reactContext);
case TimingModule.NAME:
return new TimingModule(reactContext, mReactInstanceManager.getDevSupportManager());
case UIManagerModule.NAME:
return createUIManager(reactContext);
case DeviceInfoModule.NAME:
return new DeviceInfoModule(reactContext);
default:
throw new IllegalArgumentException(
"In CoreModulesPackage, could not find Native module for " + name);
}
}
private UIManagerModule createUIManager(final ReactApplicationContext reactContext) {
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createUIManagerModule");
try {
if (mLazyViewManagersEnabled) {
ViewManagerResolver resolver =
new ViewManagerResolver() {
@Override
public @Nullable ViewManager getViewManager(String viewManagerName) {
return mReactInstanceManager.createViewManager(viewManagerName);
}
@Override
public Collection<String> getViewManagerNames() {
return mReactInstanceManager.getViewManagerNames();
}
};
return new UIManagerModule(
reactContext, resolver, mMinTimeLeftInFrameForNonBatchedOperationMs);
} else {
return new UIManagerModule(
reactContext,
mReactInstanceManager.getOrCreateViewManagers(reactContext),
mMinTimeLeftInFrameForNonBatchedOperationMs);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_END);
}
}
@Override
public void startProcessPackage() {
ReactMarker.logMarker(PROCESS_CORE_REACT_PACKAGE_START);
}
@Override
public void endProcessPackage() {
ReactMarker.logMarker(PROCESS_CORE_REACT_PACKAGE_END);
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.devsupport.JSCHeapCapture;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.debuggingoverlay.DebuggingOverlayManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Provider;
/**
* Package defining core framework modules (e.g. UIManager). It should be used for modules that
* require special integration with other framework parts (e.g. with the list of packages to load
* view managers from).
*/
@ReactModuleList(
nativeModules = {
JSCHeapCapture.class,
})
/* package */
public class DebugCorePackage extends TurboReactPackage implements ViewManagerOnDemandReactPackage {
private @Nullable Map<String, ModuleSpec> mViewManagers;
public DebugCorePackage() {}
@Override
public @Nullable NativeModule getModule(String name, ReactApplicationContext reactContext) {
switch (name) {
case JSCHeapCapture.NAME:
return new JSCHeapCapture(reactContext);
default:
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
try {
Class<?> reactModuleInfoProviderClass =
Class.forName("com.facebook.react.DebugCorePackage$$ReactModuleInfoProvider");
return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance();
} catch (ClassNotFoundException e) {
// In OSS case, the annotation processor does not run. We fall back on creating this by hand
Class<? extends NativeModule>[] moduleList = new Class[] {JSCHeapCapture.class};
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
for (Class<? extends NativeModule> moduleClass : moduleList) {
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
reactModuleInfoMap.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
reactModule.needsEagerInit(),
reactModule.isCxxModule(),
ReactModuleInfo.classIsTurboModule(moduleClass)));
}
return () -> reactModuleInfoMap;
} catch (InstantiationException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for DebugCorePackage$$ReactModuleInfoProvider", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for DebugCorePackage$$ReactModuleInfoProvider", e);
}
}
private static void appendMap(
Map<String, ModuleSpec> map, String name, Provider<? extends NativeModule> provider) {
map.put(name, ModuleSpec.viewManagerSpec(provider));
}
/** @return a map of view managers that should be registered with {@link UIManagerModule} */
private Map<String, ModuleSpec> getViewManagersMap() {
if (mViewManagers == null) {
Map<String, ModuleSpec> viewManagers = new HashMap<>();
appendMap(viewManagers, DebuggingOverlayManager.REACT_CLASS, DebuggingOverlayManager::new);
mViewManagers = viewManagers;
}
return mViewManagers;
}
@Override
public List<ModuleSpec> getViewManagers(ReactApplicationContext reactContext) {
return new ArrayList<>(getViewManagersMap().values());
}
@Override
public Collection<String> getViewManagerNames(ReactApplicationContext reactContext) {
return getViewManagersMap().keySet();
}
@Override
public @Nullable ViewManager createViewManager(
ReactApplicationContext reactContext, String viewManagerName) {
ModuleSpec spec = getViewManagersMap().get(viewManagerName);
return spec != null ? (ViewManager) spec.getProvider().get() : null;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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;
import com.facebook.react.bridge.NativeModule;
import javax.inject.Provider;
/** Provider for an already initialized and non-lazy NativeModule. */
class EagerModuleProvider implements Provider<NativeModule> {
private final NativeModule mModule;
public EagerModuleProvider(NativeModule module) {
mModule = module;
}
@Override
public NativeModule get() {
return mModule;
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.facebook.react.jstasks.HeadlessJsTaskContext;
import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Base class for running JS without a UI. Generally, you only need to override {@link
* #getTaskConfig}, which is called for every {@link #onStartCommand}. The result, if not {@code
* null}, is used to run a JS task.
*
* <p>If you need more fine-grained control over how tasks are run, you can override {@link
* #onStartCommand} and call {@link #startTask} depending on your custom logic.
*
* <p>If you're starting a {@code HeadlessJsTaskService} from a {@code BroadcastReceiver} (e.g.
* handling push notifications), make sure to call {@link #acquireWakeLockNow} before returning from
* {@link BroadcastReceiver#onReceive}, to make sure the device doesn't go to sleep before the
* service is started.
*/
public abstract class HeadlessJsTaskService extends Service implements HeadlessJsTaskEventListener {
private final Set<Integer> mActiveTasks = new CopyOnWriteArraySet<>();
private static @Nullable PowerManager.WakeLock sWakeLock;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
HeadlessJsTaskConfig taskConfig = getTaskConfig(intent);
if (taskConfig != null) {
startTask(taskConfig);
return START_REDELIVER_INTENT;
}
return START_NOT_STICKY;
}
/**
* Called from {@link #onStartCommand} to create a {@link HeadlessJsTaskConfig} for this intent.
*
* @param intent the {@link Intent} received in {@link #onStartCommand}.
* @return a {@link HeadlessJsTaskConfig} to be used with {@link #startTask}, or {@code null} to
* ignore this command.
*/
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
return null;
}
/**
* Acquire a wake lock to ensure the device doesn't go to sleep while processing background tasks.
*/
@SuppressLint("WakelockTimeout")
public static void acquireWakeLockNow(Context context) {
if (sWakeLock == null || !sWakeLock.isHeld()) {
PowerManager powerManager =
Assertions.assertNotNull((PowerManager) context.getSystemService(POWER_SERVICE));
sWakeLock =
powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, HeadlessJsTaskService.class.getCanonicalName());
sWakeLock.setReferenceCounted(false);
sWakeLock.acquire();
}
}
@Override
public @Nullable IBinder onBind(Intent intent) {
return null;
}
/**
* Start a task. This method handles starting a new React instance if required.
*
* <p>Has to be called on the UI thread.
*
* @param taskConfig describes what task to start and the parameters to pass to it
*/
protected void startTask(final HeadlessJsTaskConfig taskConfig) {
UiThreadUtil.assertOnUiThread();
acquireWakeLockNow(this);
final ReactInstanceManager reactInstanceManager =
getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
invokeStartTask(reactContext, taskConfig);
reactInstanceManager.removeReactInstanceEventListener(this);
}
});
reactInstanceManager.createReactContextInBackground();
} else {
invokeStartTask(reactContext, taskConfig);
}
}
private void invokeStartTask(ReactContext reactContext, final HeadlessJsTaskConfig taskConfig) {
final HeadlessJsTaskContext headlessJsTaskContext =
HeadlessJsTaskContext.getInstance(reactContext);
headlessJsTaskContext.addTaskEventListener(this);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
int taskId = headlessJsTaskContext.startTask(taskConfig);
mActiveTasks.add(taskId);
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (getReactNativeHost().hasInstance()) {
ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
HeadlessJsTaskContext headlessJsTaskContext =
HeadlessJsTaskContext.getInstance(reactContext);
headlessJsTaskContext.removeTaskEventListener(this);
}
}
if (sWakeLock != null) {
sWakeLock.release();
}
}
@Override
public void onHeadlessJsTaskStart(int taskId) {}
@Override
public void onHeadlessJsTaskFinish(int taskId) {
mActiveTasks.remove(taskId);
if (mActiveTasks.size() == 0) {
stopSelf();
}
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes {@link #getApplication()}
* is an instance of {@link ReactApplication} and calls {@link
* ReactApplication#getReactNativeHost()}. Override this method if your application class does not
* implement {@code ReactApplication} or you simply have a different mechanism for storing a
* {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getApplication()).getReactNativeHost();
}
}

View File

@@ -0,0 +1,17 @@
/*
* 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
/**
* An enum that specifies the algorithm to use when loading theJS Engine. [.JSC] will load
* JavaScriptCore first and fail if it is not available. [.HERMES] will load Hermes first and fail
* if it is not available.
*/
public enum class JSEngineResolutionAlgorithm {
JSC,
HERMES
}

View File

@@ -0,0 +1,132 @@
/*
* 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;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
import com.facebook.react.bridge.ModuleHolder;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.systrace.SystraceMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** React package supporting lazy creation of native modules. */
@Deprecated(since = "This class is deprecated, please use BaseReactPackage instead.")
public abstract class LazyReactPackage implements ReactPackage {
/**
* We return an iterable
*
* @param reactContext context
* @return {@link Iterable<ModuleHolder>} that contains all native modules registered for the
* context
*/
/** package */
Iterable<ModuleHolder> getNativeModuleIterator(final ReactApplicationContext reactContext) {
final Map<String, ReactModuleInfo> reactModuleInfoMap =
getReactModuleInfoProvider().getReactModuleInfos();
final List<ModuleSpec> nativeModules = getNativeModules(reactContext);
return () ->
new Iterator<ModuleHolder>() {
int position = 0;
@Override
public ModuleHolder next() {
ModuleSpec moduleSpec = nativeModules.get(position++);
String name = moduleSpec.getName();
ReactModuleInfo reactModuleInfo = reactModuleInfoMap.get(name);
ModuleHolder moduleHolder;
if (reactModuleInfo == null) {
NativeModule module;
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_START, name);
try {
module = moduleSpec.getProvider().get();
} finally {
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END);
}
moduleHolder = new ModuleHolder(module);
} else {
moduleHolder = new ModuleHolder(reactModuleInfo, moduleSpec.getProvider());
}
return moduleHolder;
}
@Override
public boolean hasNext() {
return position < nativeModules.size();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove native modules from the list");
}
};
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of module specs that can create the native modules
*/
protected abstract List<ModuleSpec> getNativeModules(ReactApplicationContext reactContext);
/**
* @param reactContext react application context that can be used to create modules
* @return {@link List<NativeModule>} to register
*/
@Override
public final List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
for (ModuleSpec holder : getNativeModules(reactContext)) {
NativeModule nativeModule;
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createNativeModule").flush();
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_START, holder.getName());
try {
nativeModule = holder.getProvider().get();
} finally {
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END);
SystraceMessage.endSection(TRACE_TAG_REACT_JAVA_BRIDGE).flush();
}
modules.add(nativeModule);
}
return modules;
}
/**
* @param reactContext react application context that can be used to create View Managers.
* @return list of module specs that can create the View Managers.
*/
public List<ModuleSpec> getViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ModuleSpec> viewManagerModuleSpecs = getViewManagers(reactContext);
if (viewManagerModuleSpecs == null || viewManagerModuleSpecs.isEmpty()) {
return Collections.emptyList();
}
List<ViewManager> viewManagers = new ArrayList<>();
for (ModuleSpec moduleSpec : viewManagerModuleSpecs) {
viewManagers.add((ViewManager) moduleSpec.getProvider().get());
}
return viewManagers;
}
public abstract ReactModuleInfoProvider getReactModuleInfoProvider();
}

View File

@@ -0,0 +1,61 @@
/*
* 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;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import com.facebook.react.bridge.MemoryPressureListener;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/** Translates and routes memory pressure events to the current catalyst instance. */
public class MemoryPressureRouter implements ComponentCallbacks2 {
private final Set<MemoryPressureListener> mListeners =
Collections.synchronizedSet(new LinkedHashSet<>());
public MemoryPressureRouter(Context context) {
context.getApplicationContext().registerComponentCallbacks(this);
}
public void destroy(Context context) {
context.getApplicationContext().unregisterComponentCallbacks(this);
}
/** Add a listener to be notified of memory pressure events. */
public void addMemoryPressureListener(MemoryPressureListener listener) {
mListeners.add(listener);
}
/** Remove a listener previously added with {@link #addMemoryPressureListener}. */
public void removeMemoryPressureListener(MemoryPressureListener listener) {
mListeners.remove(listener);
}
@Override
public void onTrimMemory(int level) {
dispatchMemoryPressure(level);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {}
@Override
public void onLowMemory() {}
private void dispatchMemoryPressure(int level) {
// copy listeners array to avoid ConcurrentModificationException if any of the listeners remove
// themselves in handleMemoryPressure()
MemoryPressureListener[] listeners =
mListeners.toArray(new MemoryPressureListener[mListeners.size()]);
for (MemoryPressureListener listener : listeners) {
listener.handleMemoryPressure(level);
}
}
}

View File

@@ -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;
import com.facebook.react.bridge.ModuleHolder;
import com.facebook.react.bridge.NativeModuleRegistry;
import com.facebook.react.bridge.ReactApplicationContext;
import java.util.HashMap;
import java.util.Map;
/** Helper class to build NativeModuleRegistry. */
public class NativeModuleRegistryBuilder {
private final ReactApplicationContext mReactApplicationContext;
private final Map<String, ModuleHolder> mModules = new HashMap<>();
public NativeModuleRegistryBuilder(
ReactApplicationContext reactApplicationContext, ReactInstanceManager reactInstanceManager) {
mReactApplicationContext = reactApplicationContext;
}
public void processPackage(ReactPackage reactPackage) {
// We use an iterable instead of an iterator here to ensure thread safety, and that this list
// cannot be modified
Iterable<ModuleHolder> moduleHolders;
if (reactPackage instanceof LazyReactPackage) {
moduleHolders =
((LazyReactPackage) reactPackage).getNativeModuleIterator(mReactApplicationContext);
} else if (reactPackage instanceof BaseReactPackage) {
moduleHolders =
((BaseReactPackage) reactPackage).getNativeModuleIterator(mReactApplicationContext);
} else {
moduleHolders =
ReactPackageHelper.getNativeModuleIterator(reactPackage, mReactApplicationContext);
}
for (ModuleHolder moduleHolder : moduleHolders) {
String name = moduleHolder.getName();
if (mModules.containsKey(name)) {
ModuleHolder existingNativeModule = mModules.get(name);
if (!moduleHolder.getCanOverrideExistingModule()) {
throw new IllegalStateException(
"Native module "
+ name
+ " tried to override "
+ existingNativeModule.getClassName()
+ ". Check the getPackages() method in MainApplication.java, it might be that module is being created twice. If this was your intention, set canOverrideExistingModule=true. "
+ "This error may also be present if the package is present only once in getPackages() but is also automatically added later during build time by autolinking. Try removing the existing entry and rebuild.");
}
mModules.remove(existingNativeModule);
}
mModules.put(name, moduleHolder);
}
}
public NativeModuleRegistry build() {
return new NativeModuleRegistry(mReactApplicationContext, mModules);
}
}

View File

@@ -0,0 +1,148 @@
/*
* 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;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import org.jetbrains.annotations.NotNull;
/** Base Activity for React Native applications. */
public abstract class ReactActivity extends AppCompatActivity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private final ReactActivityDelegate mDelegate;
protected ReactActivity() {
mDelegate = createReactActivityDelegate();
}
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component. e.g. "MoviesApp"
*/
protected @Nullable String getMainComponentName() {
return null;
}
/** Called at construction time, override if you have a custom delegate implementation. */
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
super.onPause();
mDelegate.onPause();
}
@Override
protected void onResume() {
super.onResume();
mDelegate.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mDelegate.onDestroy();
}
public void getReactDelegate() {
mDelegate.getReactDelegate();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mDelegate.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return mDelegate.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event);
}
@Override
public void onBackPressed() {
if (!mDelegate.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
if (!mDelegate.onNewIntent(intent)) {
super.onNewIntent(intent);
}
}
@Override
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {
mDelegate.requestPermissions(permissions, requestCode, listener);
}
@Override
public void onRequestPermissionsResult(
int requestCode, @NotNull String[] permissions, @NotNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mDelegate.onWindowFocusChanged(hasFocus);
}
@Override
public void onConfigurationChanged(@NotNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDelegate.onConfigurationChanged(newConfig);
}
protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}
protected final ReactInstanceManager getReactInstanceManager() {
return mDelegate.getReactInstanceManager();
}
protected final void loadApp(String appKey) {
mDelegate.loadApp(appKey);
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Callback;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.modules.core.PermissionListener;
/**
* Delegate class for {@link ReactActivity}. You can subclass this to provide custom implementations
* for e.g. {@link #getReactNativeHost()}, if your Application class doesn't implement {@link
* ReactApplication}.
*/
public class ReactActivityDelegate {
private final @Nullable Activity mActivity;
private final @Nullable String mMainComponentName;
private @Nullable PermissionListener mPermissionListener;
private @Nullable Callback mPermissionsCallback;
private ReactDelegate mReactDelegate;
@Deprecated
public ReactActivityDelegate(@Nullable Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
}
public ReactActivityDelegate(
@Nullable ReactActivity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
}
/**
* Public API to populate the launch options that will be passed to React. Here you can customize
* the values that will be passed as `initialProperties` to the Renderer.
*
* @return Either null or a key-value map as a Bundle
*/
protected @Nullable Bundle getLaunchOptions() {
return null;
}
protected @Nullable Bundle composeLaunchOptions() {
Bundle composedLaunchOptions = getLaunchOptions();
if (isFabricEnabled()) {
if (composedLaunchOptions == null) {
composedLaunchOptions = new Bundle();
}
}
return composedLaunchOptions;
}
protected ReactRootView createRootView() {
return new ReactRootView(getContext());
}
protected ReactRootView createRootView(Bundle initialProps) {
return new ReactRootView(getContext());
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes {@link
* Activity#getApplication()} is an instance of {@link ReactApplication} and calls {@link
* ReactApplication#getReactNativeHost()}. Override this method if your application class does not
* implement {@code ReactApplication} or you simply have a different mechanism for storing a
* {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
public ReactHost getReactHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactHost();
}
protected @Nullable ReactDelegate getReactDelegate() {
return mReactDelegate;
}
public ReactInstanceManager getReactInstanceManager() {
return mReactDelegate.getReactInstanceManager();
}
public String getMainComponentName() {
return mMainComponentName;
}
protected void onCreate(Bundle savedInstanceState) {
String mainComponentName = getMainComponentName();
final Bundle launchOptions = composeLaunchOptions();
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactDelegate =
new ReactDelegate(getPlainActivity(), getReactHost(), mainComponentName, launchOptions);
} else {
mReactDelegate =
new ReactDelegate(
getPlainActivity(),
getReactNativeHost(),
mainComponentName,
launchOptions,
isFabricEnabled()) {
@Override
protected ReactRootView createRootView() {
return ReactActivityDelegate.this.createRootView(launchOptions);
}
};
}
if (mainComponentName != null) {
loadApp(mainComponentName);
}
}
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
protected void onPause() {
mReactDelegate.onHostPause();
}
protected void onResume() {
mReactDelegate.onHostResume();
if (mPermissionsCallback != null) {
mPermissionsCallback.invoke();
mPermissionsCallback = null;
}
}
protected void onDestroy() {
mReactDelegate.onHostDestroy();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mReactDelegate.onActivityResult(requestCode, resultCode, data, true);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mReactDelegate.onKeyDown(keyCode, event);
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mReactDelegate.shouldShowDevMenuOrReload(keyCode, event);
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return mReactDelegate.onKeyLongPress(keyCode);
}
public boolean onBackPressed() {
return mReactDelegate.onBackPressed();
}
public boolean onNewIntent(Intent intent) {
return mReactDelegate.onNewIntent(intent);
}
public void onWindowFocusChanged(boolean hasFocus) {
mReactDelegate.onWindowFocusChanged(hasFocus);
}
public void onConfigurationChanged(Configuration newConfig) {
mReactDelegate.onConfigurationChanged(newConfig);
}
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {
mPermissionListener = listener;
getPlainActivity().requestPermissions(permissions, requestCode);
}
public void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
mPermissionsCallback =
new Callback() {
@Override
public void invoke(Object... args) {
if (mPermissionListener != null
&& mPermissionListener.onRequestPermissionsResult(
requestCode, permissions, grantResults)) {
mPermissionListener = null;
}
}
};
}
protected Context getContext() {
return Assertions.assertNotNull(mActivity);
}
protected Activity getPlainActivity() {
return ((Activity) getContext());
}
/**
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
* also control if Concurrent Root (React 18) should be enabled or not.
*
* @return true if Fabric is enabled for this Activity, false otherwise.
*/
protected boolean isFabricEnabled() {
return ReactFeatureFlags.enableFabricRenderer;
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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;
import android.view.KeyEvent;
import android.view.View;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import java.util.Map;
/** Responsible for dispatching events specific for hardware inputs. */
class ReactAndroidHWInputDeviceHelper {
/**
* Contains a mapping between handled KeyEvents and the corresponding navigation event that should
* be fired when the KeyEvent is received.
*/
private static final Map<Integer, String> KEY_EVENTS_ACTIONS =
MapBuilder.<Integer, String>builder()
.put(KeyEvent.KEYCODE_DPAD_CENTER, "select")
.put(KeyEvent.KEYCODE_ENTER, "select")
.put(KeyEvent.KEYCODE_SPACE, "select")
.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, "playPause")
.put(KeyEvent.KEYCODE_MEDIA_REWIND, "rewind")
.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, "fastForward")
.put(KeyEvent.KEYCODE_MEDIA_STOP, "stop")
.put(KeyEvent.KEYCODE_MEDIA_NEXT, "next")
.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS, "previous")
.put(KeyEvent.KEYCODE_DPAD_UP, "up")
.put(KeyEvent.KEYCODE_DPAD_RIGHT, "right")
.put(KeyEvent.KEYCODE_DPAD_DOWN, "down")
.put(KeyEvent.KEYCODE_DPAD_LEFT, "left")
.put(KeyEvent.KEYCODE_INFO, "info")
.put(KeyEvent.KEYCODE_MENU, "menu")
.build();
/**
* We keep a reference to the last focused view id so that we can send it as a target for key
* events and be able to send a blur event when focus changes.
*/
private int mLastFocusedViewId = View.NO_ID;
private final ReactRootView mReactRootView;
ReactAndroidHWInputDeviceHelper(ReactRootView mReactRootView) {
this.mReactRootView = mReactRootView;
}
/** Called from {@link ReactRootView}. This is the main place the key events are handled. */
public void handleKeyEvent(KeyEvent ev) {
int eventKeyCode = ev.getKeyCode();
int eventKeyAction = ev.getAction();
if ((eventKeyAction == KeyEvent.ACTION_UP || eventKeyAction == KeyEvent.ACTION_DOWN)
&& KEY_EVENTS_ACTIONS.containsKey(eventKeyCode)) {
dispatchEvent(KEY_EVENTS_ACTIONS.get(eventKeyCode), mLastFocusedViewId, eventKeyAction);
}
}
/** Called from {@link ReactRootView} when focused view changes. */
public void onFocusChanged(View newFocusedView) {
if (mLastFocusedViewId == newFocusedView.getId()) {
return;
}
if (mLastFocusedViewId != View.NO_ID) {
dispatchEvent("blur", mLastFocusedViewId);
}
mLastFocusedViewId = newFocusedView.getId();
dispatchEvent("focus", newFocusedView.getId());
}
/** Called from {@link ReactRootView} when the whole view hierarchy looses focus. */
public void clearFocus() {
if (mLastFocusedViewId != View.NO_ID) {
dispatchEvent("blur", mLastFocusedViewId);
}
mLastFocusedViewId = View.NO_ID;
}
private void dispatchEvent(String eventType, int targetViewId) {
dispatchEvent(eventType, targetViewId, -1);
}
private void dispatchEvent(String eventType, int targetViewId, int eventKeyAction) {
WritableMap event = new WritableNativeMap();
event.putString("eventType", eventType);
event.putInt("eventKeyAction", eventKeyAction);
if (targetViewId != View.NO_ID) {
event.putInt("tag", targetViewId);
}
mReactRootView.sendEvent("onHWKeyEvent", event);
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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
/** Interface that represents an instance of a React Native application */
public interface ReactApplication {
/** Get the default [ReactNativeHost] for this app. */
public val reactNativeHost: ReactNativeHost
/**
* Get the default [ReactHost] for this app. This method will be used by the new architecture of
* react native
*/
public val reactHost: ReactHost?
get() = null
}

View File

@@ -0,0 +1,349 @@
/*
* 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;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.devsupport.DisabledDevSupportManager;
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.interfaces.fabric.ReactSurface;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
/**
* A delegate for handling React Application support. This delegate is unaware whether it is used in
* an {@link Activity} or a {@link android.app.Fragment}.
*/
public class ReactDelegate {
private final Activity mActivity;
private ReactRootView mReactRootView;
@Nullable private final String mMainComponentName;
@Nullable private Bundle mLaunchOptions;
@Nullable private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
@Nullable private ReactNativeHost mReactNativeHost;
@Nullable private ReactHost mReactHost;
@Nullable private ReactSurface mReactSurface;
private boolean mFabricEnabled = false;
/**
* Do not use this constructor as it's not accounting for New Architecture at all. You should
* either use {@link ReactDelegate#ReactDelegate(Activity, ReactHost, String, Bundle)} if you're
* on bridgeless mode or {@link ReactDelegate#ReactDelegate(Activity, ReactNativeHost, String,
* Bundle, boolean)} and use the last parameter to toggle paper/fabric.
*
* @deprecated Use one of the other constructors instead to account for New Architecture.
*/
@Deprecated
public ReactDelegate(
Activity activity,
ReactNativeHost reactNativeHost,
@Nullable String appKey,
@Nullable Bundle launchOptions) {
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = launchOptions;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactNativeHost = reactNativeHost;
}
public ReactDelegate(
Activity activity,
ReactHost reactHost,
@Nullable String appKey,
@Nullable Bundle launchOptions) {
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = launchOptions;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactHost = reactHost;
}
public ReactDelegate(
Activity activity,
ReactNativeHost reactNativeHost,
@Nullable String appKey,
@Nullable Bundle launchOptions,
boolean fabricEnabled) {
mFabricEnabled = fabricEnabled;
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = composeLaunchOptions(launchOptions);
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactNativeHost = reactNativeHost;
}
@Nullable
private DevSupportManager getDevSupportManager() {
if (ReactFeatureFlags.enableBridgelessArchitecture
&& mReactHost != null
&& mReactHost.getDevSupportManager() != null) {
return mReactHost.getDevSupportManager();
} else if (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()) {
return getReactNativeHost().getReactInstanceManager().getDevSupportManager();
} else {
return null;
}
}
public void onHostResume() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
if (mActivity instanceof DefaultHardwareBackBtnHandler) {
mReactHost.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
}
} else {
if (getReactNativeHost().hasInstance()) {
if (mActivity instanceof DefaultHardwareBackBtnHandler) {
getReactNativeHost()
.getReactInstanceManager()
.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
} else {
throw new ClassCastException(
"Host Activity does not implement DefaultHardwareBackBtnHandler");
}
}
}
}
public void onHostPause() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onHostPause(mActivity);
} else {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostPause(mActivity);
}
}
}
public void onHostDestroy() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onHostDestroy(mActivity);
} else {
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostDestroy(mActivity);
}
}
}
public boolean onBackPressed() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onBackPressed();
return true;
} else {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onBackPressed();
return true;
}
}
return false;
}
public boolean onNewIntent(Intent intent) {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onNewIntent(intent);
return true;
} else {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onNewIntent(intent);
return true;
}
}
return false;
}
public void onActivityResult(
int requestCode, int resultCode, Intent data, boolean shouldForwardToReactInstance) {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onActivityResult(mActivity, requestCode, resultCode, data);
} else {
if (getReactNativeHost().hasInstance() && shouldForwardToReactInstance) {
getReactNativeHost()
.getReactInstanceManager()
.onActivityResult(mActivity, requestCode, resultCode, data);
}
}
}
public void onWindowFocusChanged(boolean hasFocus) {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onWindowFocusChange(hasFocus);
} else {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
}
}
}
public void onConfigurationChanged(Configuration newConfig) {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onConfigurationChanged(Assertions.assertNotNull(mActivity));
} else {
if (getReactNativeHost().hasInstance()) {
getReactInstanceManager()
.onConfigurationChanged(Assertions.assertNotNull(mActivity), newConfig);
}
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
&& ((ReactFeatureFlags.enableBridgelessArchitecture
&& mReactHost != null
&& mReactHost.getDevSupportManager() != null)
|| (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()))) {
event.startTracking();
return true;
}
return false;
}
public boolean onKeyLongPress(int keyCode) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
if (ReactFeatureFlags.enableBridgelessArchitecture
&& mReactHost != null
&& mReactHost.getDevSupportManager() != null) {
mReactHost.getDevSupportManager().showDevOptionsDialog();
return true;
} else {
if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) {
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
}
}
return false;
}
public void reload() {
DevSupportManager devSupportManager = getDevSupportManager();
if (devSupportManager != null) {
// With Bridgeless enabled, reload in RELEASE mode
if (devSupportManager instanceof DisabledDevSupportManager
&& ReactFeatureFlags.enableBridgelessArchitecture
&& mReactHost != null) {
// Do not reload the bundle from JS as there is no bundler running in release mode.
mReactHost.reload("ReactDelegate.reload()");
} else {
devSupportManager.handleReloadJS();
}
}
}
public void loadApp() {
loadApp(mMainComponentName);
}
public void loadApp(String appKey) {
// With Bridgeless enabled, create and start the surface
if (ReactFeatureFlags.enableBridgelessArchitecture) {
if (mReactSurface == null) {
// Create a ReactSurface
mReactSurface = mReactHost.createSurface(mActivity, appKey, mLaunchOptions);
// Set main Activity's content view
mActivity.setContentView(mReactSurface.getView());
}
mReactSurface.start();
} else {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
}
public ReactRootView getReactRootView() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
return (ReactRootView) mReactSurface.getView();
} else {
return mReactRootView;
}
}
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(mActivity);
reactRootView.setIsFabric(isFabricEnabled());
return reactRootView;
}
/**
* Handles delegating the {@link Activity#onKeyUp(int, KeyEvent)} method to determine whether the
* application should show the developer menu or should reload the React Application.
*
* @return true if we consume the event and either shoed the develop menu or reloaded the
* application.
*/
public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) {
DevSupportManager devSupportManager = getDevSupportManager();
if (devSupportManager == null) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_MENU) {
devSupportManager.showDevOptionsDialog();
return true;
}
boolean didDoubleTapR =
Assertions.assertNotNull(mDoubleTapReloadRecognizer)
.didDoubleTapR(keyCode, mActivity.getCurrentFocus());
if (didDoubleTapR) {
devSupportManager.handleReloadJS();
return true;
}
return false;
}
/** Get the {@link ReactNativeHost} used by this app. */
private ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
public ReactInstanceManager getReactInstanceManager() {
return getReactNativeHost().getReactInstanceManager();
}
/**
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
* also control if Concurrent Root (React 18) should be enabled or not.
*
* @return true if Fabric is enabled for this Activity, false otherwise.
*/
protected boolean isFabricEnabled() {
return mFabricEnabled;
}
private @NonNull Bundle composeLaunchOptions(Bundle composedLaunchOptions) {
if (isFabricEnabled()) {
if (composedLaunchOptions == null) {
composedLaunchOptions = new Bundle();
}
}
return composedLaunchOptions;
}
}

View File

@@ -0,0 +1,218 @@
/*
* 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;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
/**
* Fragment for creating a React View. This allows the developer to "embed" a React Application
* inside native components such as a Drawer, ViewPager, etc.
*/
public class ReactFragment extends Fragment implements PermissionAwareActivity {
protected static final String ARG_COMPONENT_NAME = "arg_component_name";
protected static final String ARG_LAUNCH_OPTIONS = "arg_launch_options";
protected static final String ARG_FABRIC_ENABLED = "arg_fabric_enabled";
protected ReactDelegate mReactDelegate;
@Nullable private PermissionListener mPermissionListener;
public ReactFragment() {
// Required empty public constructor
}
/**
* @param componentName The name of the react native component
* @param fabricEnabled Flag to enable Fabric for ReactFragment
* @return A new instance of fragment ReactFragment.
*/
private static ReactFragment newInstance(
String componentName, Bundle launchOptions, Boolean fabricEnabled) {
ReactFragment fragment = new ReactFragment();
Bundle args = new Bundle();
args.putString(ARG_COMPONENT_NAME, componentName);
args.putBundle(ARG_LAUNCH_OPTIONS, launchOptions);
args.putBoolean(ARG_FABRIC_ENABLED, fabricEnabled);
fragment.setArguments(args);
return fragment;
}
// region Lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String mainComponentName = null;
Bundle launchOptions = null;
Boolean fabricEnabled = null;
if (getArguments() != null) {
mainComponentName = getArguments().getString(ARG_COMPONENT_NAME);
launchOptions = getArguments().getBundle(ARG_LAUNCH_OPTIONS);
fabricEnabled = getArguments().getBoolean(ARG_FABRIC_ENABLED);
}
if (mainComponentName == null) {
throw new IllegalStateException("Cannot loadApp if component name is null");
}
mReactDelegate =
new ReactDelegate(
getActivity(), getReactNativeHost(), mainComponentName, launchOptions, fabricEnabled);
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes {@link
* Activity#getApplication()} is an instance of {@link ReactApplication} and calls {@link
* ReactApplication#getReactNativeHost()}. Override this method if your application class does not
* implement {@code ReactApplication} or you simply have a different mechanism for storing a
* {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getActivity().getApplication()).getReactNativeHost();
}
protected ReactDelegate getReactDelegate() {
return mReactDelegate;
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mReactDelegate.loadApp();
return mReactDelegate.getReactRootView();
}
@Override
public void onResume() {
super.onResume();
mReactDelegate.onHostResume();
}
@Override
public void onPause() {
super.onPause();
mReactDelegate.onHostPause();
}
@Override
public void onDestroy() {
super.onDestroy();
mReactDelegate.onHostDestroy();
}
// endregion
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mReactDelegate.onActivityResult(requestCode, resultCode, data, false);
}
/**
* Helper to forward hardware back presses to our React Native Host
*
* <p>This must be called via a forward from your host Activity
*/
public boolean onBackPressed() {
return mReactDelegate.onBackPressed();
}
/**
* Helper to forward onKeyUp commands from our host Activity. This allows ReactFragment to handle
* double tap reloads and dev menus
*
* <p>This must be called via a forward from your host Activity
*
* @param keyCode keyCode
* @param event event
* @return true if we handled onKeyUp
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mReactDelegate.shouldShowDevMenuOrReload(keyCode, event);
}
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (mPermissionListener != null
&& mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
mPermissionListener = null;
}
}
@Override
public int checkPermission(String permission, int pid, int uid) {
return getActivity().checkPermission(permission, pid, uid);
}
@Override
public int checkSelfPermission(String permission) {
return getActivity().checkSelfPermission(permission);
}
@Override
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {
mPermissionListener = listener;
requestPermissions(permissions, requestCode);
}
/** Builder class to help instantiate a ReactFragment */
public static class Builder {
@Nullable String mComponentName;
@Nullable Bundle mLaunchOptions;
@Nullable Boolean mFabricEnabled;
public Builder() {
mComponentName = null;
mLaunchOptions = null;
mFabricEnabled = false;
}
/**
* Set the Component name for our React Native instance.
*
* @param componentName The name of the component
* @return Builder
*/
public Builder setComponentName(String componentName) {
mComponentName = componentName;
return this;
}
/**
* Set the Launch Options for our React Native instance.
*
* @param launchOptions launchOptions
* @return Builder
*/
public Builder setLaunchOptions(Bundle launchOptions) {
mLaunchOptions = launchOptions;
return this;
}
public ReactFragment build() {
return ReactFragment.newInstance(mComponentName, mLaunchOptions, mFabricEnabled);
}
public Builder setFabricEnabled(boolean fabricEnabled) {
mFabricEnabled = fabricEnabled;
return this;
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.queue.ReactQueueConfiguration
import com.facebook.react.common.LifecycleState
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.interfaces.TaskInterface
import com.facebook.react.interfaces.fabric.ReactSurface
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
/**
* A ReactHost is an object that manages a single {@link ReactInstance}. A ReactHost can be
* constructed without initializing the ReactInstance, and it will continue to exist after the
* instance is destroyed.
*
* The implementation of this interface should be Thread Safe
*/
public interface ReactHost {
/** The current [LifecycleState] for React Host */
public val lifecycleState: LifecycleState
/**
* The current [ReactContext] associated with ReactInstance. It could be nullable if ReactInstance
* hasn't been created.
*/
public val currentReactContext: ReactContext?
// TODO: review if DevSupportManager should be nullable
/** [DevSupportManager] used by this ReactHost */
public val devSupportManager: DevSupportManager?
// TODO: review if possible to remove ReactQueueConfiguration
/** [ReactQueueConfiguration] for caller to post jobs in React Native threads */
public val reactQueueConfiguration: ReactQueueConfiguration?
/** [JSEngineResolutionAlgorithm] used by this host. */
public var jsEngineResolutionAlgorithm: JSEngineResolutionAlgorithm?
/** To be called when back button is pressed */
public fun onBackPressed(): Boolean
// TODO: review why activity is nullable in all of the lifecycle methods
/** To be called when the host activity is resumed. */
public fun onHostResume(
activity: Activity?,
defaultBackButtonImpl: DefaultHardwareBackBtnHandler?
)
/** To be called when the host activity is resumed. */
public fun onHostResume(activity: Activity?)
/** To be called when the host activity is paused. */
public fun onHostPause(activity: Activity?)
/** To be called when the host activity is paused. */
public fun onHostPause()
/** To be called when the host activity is destroyed. */
public fun onHostDestroy()
/** To be called when the host activity is destroyed. */
public fun onHostDestroy(activity: Activity?)
/** To be called to create and setup an ReactSurface. */
public fun createSurface(
context: Context,
moduleName: String,
initialProps: Bundle?
): ReactSurface?
/**
* This function can be used to initialize the ReactInstance in a background thread before a
* surface needs to be rendered. It is not necessary to call this function; startSurface() will
* initialize the ReactInstance if it hasn't been preloaded.
*
* @return A Task that completes when the instance is initialized. The task will be faulted if any
* errors occur during initialization, and will be cancelled if ReactHost.destroy() is called
* before it completes.
*/
public fun start(): TaskInterface<Void>
/**
* Entrypoint to reload the ReactInstance. If the ReactInstance is destroying, will wait until
* destroy is finished, before reloading.
*
* @param reason describing why ReactHost is being reloaded (e.g. js error, user tap on reload
* button)
* @return A task that completes when React Native reloads
*/
public fun reload(reason: String): TaskInterface<Void>
/**
* Entrypoint to destroy the ReactInstance. If the ReactInstance is reloading, will wait until
* reload is finished, before destroying.
*
* @param reason describing why ReactHost is being destroyed (e.g. memmory pressure)
* @param ex exception that caused the trigger to destroy ReactHost (or null) This exception will
* be used to log properly the cause of destroy operation.
* @return A task that completes when React Native gets destroyed.
*/
public fun destroy(reason: String, ex: Exception?): TaskInterface<Void>
/* To be called when the host activity receives an activity result. */
public fun onActivityResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?,
)
/* To be called when focus has changed for the hosting window. */
public fun onWindowFocusChange(hasFocus: Boolean)
/* This method will give JS the opportunity to receive intents via Linking. */
public fun onNewIntent(intent: Intent)
public fun onConfigurationChanged(context: Context)
public fun addBeforeDestroyListener(onBeforeDestroy: () -> Unit)
public fun removeBeforeDestroyListener(onBeforeDestroy: () -> Unit)
}

View File

@@ -0,0 +1,19 @@
/*
* 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
import com.facebook.react.bridge.ReactContext
/** Interface to subscribe for react instance events */
public interface ReactInstanceEventListener {
/**
* Called when the react context is initialized (all modules registered). Always called on the UI
* thread.
*/
public fun onReactContextInitialized(context: ReactContext)
}

View File

@@ -0,0 +1,398 @@
/*
* 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;
import static com.facebook.react.ReactInstanceManager.initializeSoLoaderIfNecessary;
import static com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriendlyDeviceName;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.hermes.reactexecutor.HermesExecutor;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.bridge.UIManagerProvider;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.common.SurfaceDelegateFactory;
import com.facebook.react.common.annotations.StableReactNativeAPI;
import com.facebook.react.devsupport.DefaultDevSupportManagerFactory;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.devsupport.interfaces.RedBoxHandler;
import com.facebook.react.internal.ChoreographerProvider;
import com.facebook.react.jscexecutor.JSCExecutor;
import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.packagerconnection.RequestHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Builder class for {@link ReactInstanceManager} */
@StableReactNativeAPI
public class ReactInstanceManagerBuilder {
private static final String TAG = ReactInstanceManagerBuilder.class.getSimpleName();
private final List<ReactPackage> mPackages = new ArrayList<>();
private @Nullable String mJSBundleAssetUrl;
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable String mJSMainModulePath;
private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
private @Nullable Application mApplication;
private boolean mUseDeveloperSupport;
private @Nullable DevSupportManagerFactory mDevSupportManagerFactory;
private boolean mRequireActivity;
private @Nullable LifecycleState mInitialLifecycleState;
private @Nullable JSExceptionHandler mJSExceptionHandler;
private @Nullable Activity mCurrentActivity;
private @Nullable DefaultHardwareBackBtnHandler mDefaultHardwareBackBtnHandler;
private @Nullable RedBoxHandler mRedBoxHandler;
private boolean mLazyViewManagersEnabled;
private @Nullable DevBundleDownloadListener mDevBundleDownloadListener;
private @Nullable JavaScriptExecutorFactory mJavaScriptExecutorFactory;
private int mMinNumShakes = 1;
private int mMinTimeLeftInFrameForNonBatchedOperationMs = -1;
private @Nullable UIManagerProvider mUIManagerProvider;
private @Nullable Map<String, RequestHandler> mCustomPackagerCommandHandlers;
private @Nullable ReactPackageTurboModuleManagerDelegate.Builder mTMMDelegateBuilder;
private @Nullable SurfaceDelegateFactory mSurfaceDelegateFactory;
private @Nullable DevLoadingViewManager mDevLoadingViewManager;
private @Nullable JSEngineResolutionAlgorithm mJSEngineResolutionAlgorithm = null;
private @Nullable ChoreographerProvider mChoreographerProvider = null;
/* package protected */ ReactInstanceManagerBuilder() {}
/** Factory for desired implementation of JavaScriptExecutor. */
public ReactInstanceManagerBuilder setJavaScriptExecutorFactory(
@Nullable JavaScriptExecutorFactory javaScriptExecutorFactory) {
mJavaScriptExecutorFactory = javaScriptExecutorFactory;
return this;
}
public ReactInstanceManagerBuilder setUIManagerProvider(UIManagerProvider uIManagerProvider) {
mUIManagerProvider = uIManagerProvider;
return this;
}
/**
* Name of the JS bundle file to be loaded from application's raw assets. Example: {@code
* "index.android.js"}
*/
public ReactInstanceManagerBuilder setBundleAssetName(String bundleAssetName) {
mJSBundleAssetUrl = (bundleAssetName == null ? null : "assets://" + bundleAssetName);
mJSBundleLoader = null;
return this;
}
/**
* Path to the JS bundle file to be loaded from the file system.
*
* <p>Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
*/
public ReactInstanceManagerBuilder setJSBundleFile(String jsBundleFile) {
if (jsBundleFile.startsWith("assets://")) {
mJSBundleAssetUrl = jsBundleFile;
mJSBundleLoader = null;
return this;
}
return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
}
/**
* Bundle loader to use when setting up JS environment. This supersedes prior invocations of
* {@link #setJSBundleFile} and {@link #setBundleAssetName}.
*
* <p>Example: {@code JSBundleLoader.createFileLoader(application, bundleFile)}
*/
public ReactInstanceManagerBuilder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
mJSBundleLoader = jsBundleLoader;
mJSBundleAssetUrl = null;
return this;
}
/**
* Sets the JS Engine to load as either Hermes or JSC. If not set, the default is JSC with a
* Hermes fallback.
*/
public ReactInstanceManagerBuilder setJSEngineResolutionAlgorithm(
@Nullable JSEngineResolutionAlgorithm jsEngineResolutionAlgorithm) {
mJSEngineResolutionAlgorithm = jsEngineResolutionAlgorithm;
return this;
}
/**
* Path to your app's main module on Metro. This is used when reloading JS during development. All
* paths are relative to the root folder the packager is serving files from. Examples: {@code
* "index.android"} or {@code "subdirectory/index.android"}
*/
public ReactInstanceManagerBuilder setJSMainModulePath(String jsMainModulePath) {
mJSMainModulePath = jsMainModulePath;
return this;
}
public ReactInstanceManagerBuilder addPackage(ReactPackage reactPackage) {
mPackages.add(reactPackage);
return this;
}
public ReactInstanceManagerBuilder addPackages(List<ReactPackage> reactPackages) {
mPackages.addAll(reactPackages);
return this;
}
public ReactInstanceManagerBuilder setBridgeIdleDebugListener(
NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener) {
mBridgeIdleDebugListener = bridgeIdleDebugListener;
return this;
}
/** Required. This must be your {@code Application} instance. */
public ReactInstanceManagerBuilder setApplication(Application application) {
mApplication = application;
return this;
}
public ReactInstanceManagerBuilder setCurrentActivity(Activity activity) {
mCurrentActivity = activity;
return this;
}
public ReactInstanceManagerBuilder setDefaultHardwareBackBtnHandler(
DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler) {
mDefaultHardwareBackBtnHandler = defaultHardwareBackBtnHandler;
return this;
}
/**
* When {@code true}, developer options such as JS reloading and debugging are enabled. Note you
* still have to call {@link #showDevOptionsDialog} to show the dev menu, e.g. when the device
* Menu button is pressed.
*/
public ReactInstanceManagerBuilder setUseDeveloperSupport(boolean useDeveloperSupport) {
mUseDeveloperSupport = useDeveloperSupport;
return this;
}
/**
* Set the custom {@link DevSupportManagerFactory}. If not set, will use {@link
* DefaultDevSupportManagerFactory}.
*/
public ReactInstanceManagerBuilder setDevSupportManagerFactory(
final DevSupportManagerFactory devSupportManagerFactory) {
mDevSupportManagerFactory = devSupportManagerFactory;
return this;
}
/**
* When {@code false}, indicates that correct usage of React Native will NOT involve an Activity.
* For the vast majority of Android apps in the ecosystem, this will not need to change. Unless
* you really know what you're doing, you should probably not change this!
*/
public ReactInstanceManagerBuilder setRequireActivity(boolean requireActivity) {
mRequireActivity = requireActivity;
return this;
}
/**
* When the {@link SurfaceDelegateFactory} is provided, it will be used for native modules to get
* a {@link SurfaceDelegate} to interact with the platform specific surface that they that needs
* to be rendered in. For mobile platform this is default to be null so that these modules will
* need to provide a default surface delegate. One example of such native module is LogBoxModule,
* which is rendered in mobile platform with LogBoxDialog, while in VR platform with custom layer
* provided by runtime.
*/
public ReactInstanceManagerBuilder setSurfaceDelegateFactory(
@Nullable final SurfaceDelegateFactory surfaceDelegateFactory) {
mSurfaceDelegateFactory = surfaceDelegateFactory;
return this;
}
/** Sets the Dev Loading View Manager. */
public ReactInstanceManagerBuilder setDevLoadingViewManager(
@Nullable DevLoadingViewManager devLoadingViewManager) {
mDevLoadingViewManager = devLoadingViewManager;
return this;
}
/**
* Sets the initial lifecycle state of the host. For example, if the host is already resumed at
* creation time, we wouldn't expect an onResume call until we get an onPause call.
*/
public ReactInstanceManagerBuilder setInitialLifecycleState(
LifecycleState initialLifecycleState) {
mInitialLifecycleState = initialLifecycleState;
return this;
}
/**
* Set the exception handler for all native module calls. If not set, the default {@link
* DevSupportManager} will be used, which shows a redbox in dev mode and rethrows (crashes the
* app) in prod mode.
*/
public ReactInstanceManagerBuilder setJSExceptionHandler(JSExceptionHandler handler) {
mJSExceptionHandler = handler;
return this;
}
public ReactInstanceManagerBuilder setRedBoxHandler(@Nullable RedBoxHandler redBoxHandler) {
mRedBoxHandler = redBoxHandler;
return this;
}
public ReactInstanceManagerBuilder setLazyViewManagersEnabled(boolean lazyViewManagersEnabled) {
mLazyViewManagersEnabled = lazyViewManagersEnabled;
return this;
}
public ReactInstanceManagerBuilder setDevBundleDownloadListener(
@Nullable DevBundleDownloadListener listener) {
mDevBundleDownloadListener = listener;
return this;
}
public ReactInstanceManagerBuilder setMinNumShakes(int minNumShakes) {
mMinNumShakes = minNumShakes;
return this;
}
public ReactInstanceManagerBuilder setMinTimeLeftInFrameForNonBatchedOperationMs(
int minTimeLeftInFrameForNonBatchedOperationMs) {
mMinTimeLeftInFrameForNonBatchedOperationMs = minTimeLeftInFrameForNonBatchedOperationMs;
return this;
}
public ReactInstanceManagerBuilder setCustomPackagerCommandHandlers(
Map<String, RequestHandler> customPackagerCommandHandlers) {
mCustomPackagerCommandHandlers = customPackagerCommandHandlers;
return this;
}
public ReactInstanceManagerBuilder setReactPackageTurboModuleManagerDelegateBuilder(
@Nullable ReactPackageTurboModuleManagerDelegate.Builder builder) {
mTMMDelegateBuilder = builder;
return this;
}
public ReactInstanceManagerBuilder setChoreographerProvider(
@Nullable ChoreographerProvider choreographerProvider) {
mChoreographerProvider = choreographerProvider;
return this;
}
/**
* Instantiates a new {@link ReactInstanceManager}. Before calling {@code build}, the following
* must be called:
*
* <ul>
* <li>{@link #setApplication}
* <li>{@link #setCurrentActivity} if the activity has already resumed
* <li>{@link #setDefaultHardwareBackBtnHandler} if the activity has already resumed
* <li>{@link #setJSBundleFile} or {@link #setJSMainModulePath}
* </ul>
*/
public ReactInstanceManager build() {
Assertions.assertNotNull(
mApplication, "Application property has not been set with this builder");
if (mInitialLifecycleState == LifecycleState.RESUMED) {
Assertions.assertNotNull(
mCurrentActivity, "Activity needs to be set if initial lifecycle state is resumed");
}
Assertions.assertCondition(
mUseDeveloperSupport || mJSBundleAssetUrl != null || mJSBundleLoader != null,
"JS Bundle File or Asset URL has to be provided when dev support is disabled");
Assertions.assertCondition(
mJSMainModulePath != null || mJSBundleAssetUrl != null || mJSBundleLoader != null,
"Either MainModulePath or JS Bundle File needs to be provided");
// We use the name of the device and the app for debugging & metrics
//noinspection ConstantConditions
String appName = mApplication.getPackageName();
String deviceName = getFriendlyDeviceName();
return new ReactInstanceManager(
mApplication,
mCurrentActivity,
mDefaultHardwareBackBtnHandler,
mJavaScriptExecutorFactory == null
? getDefaultJSExecutorFactory(appName, deviceName, mApplication.getApplicationContext())
: mJavaScriptExecutorFactory,
(mJSBundleLoader == null && mJSBundleAssetUrl != null)
? JSBundleLoader.createAssetLoader(
mApplication, mJSBundleAssetUrl, false /*Asynchronous*/)
: mJSBundleLoader,
mJSMainModulePath,
mPackages,
mUseDeveloperSupport,
mDevSupportManagerFactory == null
? new DefaultDevSupportManagerFactory()
: mDevSupportManagerFactory,
mRequireActivity,
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mJSExceptionHandler,
mRedBoxHandler,
mLazyViewManagersEnabled,
mDevBundleDownloadListener,
mMinNumShakes,
mMinTimeLeftInFrameForNonBatchedOperationMs,
mUIManagerProvider,
mCustomPackagerCommandHandlers,
mTMMDelegateBuilder,
mSurfaceDelegateFactory,
mDevLoadingViewManager,
mChoreographerProvider);
}
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(
String appName, String deviceName, Context applicationContext) {
initializeSoLoaderIfNecessary(applicationContext);
// Hermes has been enabled by default in OSS since React Native 0.70.
// If the user hasn't specified a JSEngineResolutionAlgorithm,
// we attempt to load Hermes first, and fallback to JSC if we can't resolve the library.
if (mJSEngineResolutionAlgorithm == null) {
try {
HermesExecutor.loadLibrary();
return new HermesExecutorFactory();
} catch (UnsatisfiedLinkError ignoredHermesError) {
try {
JSCExecutor.loadLibrary();
return new JSCExecutorFactory(appName, deviceName);
} catch (UnsatisfiedLinkError jscError) {
FLog.e(
TAG,
"Unable to load neither the Hermes nor the JSC native library. "
+ "Your application is not built correctly and will fail to execute");
if (jscError.getMessage().contains("__cxa_bad_typeid")) {
throw jscError;
}
return null;
}
}
} else if (mJSEngineResolutionAlgorithm == JSEngineResolutionAlgorithm.HERMES) {
HermesExecutor.loadLibrary();
return new HermesExecutorFactory();
} else {
JSCExecutor.loadLibrary();
return new JSCExecutorFactory(appName, deviceName);
}
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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;
import android.app.Application;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.UIManagerProvider;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.common.SurfaceDelegate;
import com.facebook.react.common.SurfaceDelegateFactory;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager;
import com.facebook.react.devsupport.interfaces.RedBoxHandler;
import com.facebook.react.internal.ChoreographerProvider;
import java.util.List;
/**
* Simple class that holds an instance of {@link ReactInstanceManager}. This can be used in your
* {@link Application class} (see {@link ReactApplication}), or as a static field.
*/
@DeprecatedInNewArchitecture(
message =
"This class will be replaced by com.facebook.react.ReactHost in the new architecture of React Native.")
public abstract class ReactNativeHost {
private final Application mApplication;
private @Nullable ReactInstanceManager mReactInstanceManager;
protected ReactNativeHost(Application application) {
mApplication = application;
}
/** Get the current {@link ReactInstanceManager} instance, or create one. */
public ReactInstanceManager getReactInstanceManager() {
if (mReactInstanceManager == null) {
ReactMarker.logMarker(ReactMarkerConstants.INIT_REACT_RUNTIME_START);
ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_START);
mReactInstanceManager = createReactInstanceManager();
ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_END);
}
return mReactInstanceManager;
}
/**
* Get whether this holder contains a {@link ReactInstanceManager} instance, or not. I.e. if
* {@link #getReactInstanceManager()} has been called at least once since this object was created
* or {@link #clear()} was called.
*/
public boolean hasInstance() {
return mReactInstanceManager != null;
}
/**
* Destroy the current instance and release the internal reference to it, allowing it to be GCed.
*/
public void clear() {
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
}
protected ReactInstanceManager createReactInstanceManager() {
ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
ReactInstanceManagerBuilder builder =
ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModulePath(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setDevSupportManagerFactory(getDevSupportManagerFactory())
.setDevLoadingViewManager(getDevLoadingViewManager())
.setRequireActivity(getShouldRequireActivity())
.setSurfaceDelegateFactory(getSurfaceDelegateFactory())
.setLazyViewManagersEnabled(getLazyViewManagersEnabled())
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIManagerProvider(getUIManagerProvider())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.setReactPackageTurboModuleManagerDelegateBuilder(
getReactPackageTurboModuleManagerDelegateBuilder())
.setJSEngineResolutionAlgorithm(getJSEngineResolutionAlgorithm())
.setChoreographerProvider(getChoreographerProvider());
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
ReactInstanceManager reactInstanceManager = builder.build();
ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_END);
return reactInstanceManager;
}
/** Get the {@link RedBoxHandler} to send RedBox-related callbacks to. */
protected @Nullable RedBoxHandler getRedBoxHandler() {
return null;
}
/** Get the {@link JavaScriptExecutorFactory}. Override this to use a custom Executor. */
protected @Nullable JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
return null;
}
protected @Nullable ReactPackageTurboModuleManagerDelegate.Builder
getReactPackageTurboModuleManagerDelegateBuilder() {
return null;
}
protected final Application getApplication() {
return mApplication;
}
protected @Nullable UIManagerProvider getUIManagerProvider() {
return reactApplicationContext -> null;
}
/** Returns whether or not to treat it as normal if Activity is null. */
public boolean getShouldRequireActivity() {
return true;
}
/**
* Returns whether view managers should be created lazily. See {@link
* ViewManagerOnDemandReactPackage} for details.
*
* @experimental
*/
public boolean getLazyViewManagersEnabled() {
return false;
}
/**
* Return the {@link SurfaceDelegateFactory} used by NativeModules to get access to a {@link
* SurfaceDelegate} to interact with a surface. By default in the mobile platform the {@link
* SurfaceDelegate} it returns is null, and the NativeModule needs to implement its own {@link
* SurfaceDelegate} to decide how it would interact with its own container surface.
*/
public SurfaceDelegateFactory getSurfaceDelegateFactory() {
return new SurfaceDelegateFactory() {
@Override
public @Nullable SurfaceDelegate createSurfaceDelegate(String moduleName) {
return null;
}
};
}
/**
* Get the {@link DevLoadingViewManager}. Override this to use a custom dev loading view manager
*/
protected @Nullable DevLoadingViewManager getDevLoadingViewManager() {
return null;
}
/**
* Returns the name of the main module. Determines the URL used to fetch the JS bundle from Metro.
* It is only used when dev support is enabled. This is the first file to be executed once the
* {@link ReactInstanceManager} is created. e.g. "index.android"
*/
protected String getJSMainModuleName() {
return "index.android";
}
/**
* Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
* from a custom path. By default it is loaded from Android assets, from a path specified by
* {@link getBundleAssetName}. e.g. "file://sdcard/myapp_cache/index.android.bundle"
*/
protected @Nullable String getJSBundleFile() {
return null;
}
/**
* Returns the name of the bundle in assets. If this is null, and no file path is specified for
* the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will always
* try to load the JS bundle from Metro. e.g. "index.android.bundle"
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
}
/** Returns whether dev mode should be enabled. This enables e.g. the dev menu. */
public abstract boolean getUseDeveloperSupport();
/** Get the {@link DevSupportManagerFactory}. Override this to use a custom dev support manager */
protected @Nullable DevSupportManagerFactory getDevSupportManagerFactory() {
return null;
}
/**
* Returns a list of {@link ReactPackage} used by the app. You'll most likely want to return at
* least the {@code MainReactPackage}. If your app uses additional views or modules besides the
* default ones, you'll want to include more packages here.
*/
protected abstract List<ReactPackage> getPackages();
/**
* Returns the {@link JSEngineResolutionAlgorithm} to be used when loading the JS engine. If null,
* will try to load JSC first and fallback to Hermes if JSC is not available.
*/
protected @Nullable JSEngineResolutionAlgorithm getJSEngineResolutionAlgorithm() {
return null;
}
/**
* Returns a custom implementation of ChoreographerProvider to be used this host. If null - React
* will use default direct android.view.Choreographer-based provider.
*/
protected @Nullable ChoreographerProvider getChoreographerProvider() {
return null;
}
}

View File

@@ -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;
import androidx.annotation.NonNull;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.List;
import javax.annotation.Nullable;
/**
* Main interface for providing additional capabilities to the catalyst framework by couple of
* different means:
*
* <ol>
* <li>Registering new native modules
* <li>Registering new JS modules that may be accessed from native modules or from other parts of
* the native code (requiring JS modules from the package doesn't mean it will automatically
* be included as a part of the JS bundle, so there should be a corresponding piece of code on
* JS side that will require implementation of that JS module so that it gets bundled)
* <li>Registering custom native views (view managers) and custom event types
* <li>Registering natively packaged assets/resources (e.g. images) exposed to JS
* </ol>
*
* <p>TODO(6788500, 6788507): Implement support for adding custom views, events and resources
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public interface ReactPackage {
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance This method
* is deprecated in the new Architecture of React Native.
*/
@NonNull
@DeprecatedInNewArchitecture
List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext);
/** @return a list of view managers that should be registered with {@link UIManagerModule} */
@NonNull
List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext);
/**
* Given a module name, it returns an instance of {@link NativeModule} for the name
*
* @param name name of the Native Module
* @param reactContext {@link ReactApplicationContext} context for this
*/
@Nullable
@UnstableReactNativeAPI
default NativeModule getModule(
@NonNull String name, @NonNull ReactApplicationContext reactContext) {
return null;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ModuleHolder;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.ReactConstants;
import java.util.Iterator;
import java.util.List;
class ReactPackageHelper {
/**
* A helper method to iterate over a list of Native Modules and convert them to an iterable.
*
* @param reactPackage
* @param reactApplicationContext
* @param reactInstanceManager
* @return
*/
/** package */
static Iterable<ModuleHolder> getNativeModuleIterator(
ReactPackage reactPackage, ReactApplicationContext reactApplicationContext) {
FLog.d(
ReactConstants.TAG,
reactPackage.getClass().getSimpleName()
+ " is not a LazyReactPackage, falling back to old version.");
final List<NativeModule> nativeModules =
reactPackage.createNativeModules(reactApplicationContext);
return () ->
new Iterator<ModuleHolder>() {
int position = 0;
@Override
public ModuleHolder next() {
return new ModuleHolder(nativeModules.get(position++));
}
@Override
public boolean hasNext() {
return position < nativeModules.size();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove methods ");
}
};
}
}

View File

@@ -0,0 +1,16 @@
/*
* 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;
/** Interface for the bridge to call for TTI start and end markers. */
public interface ReactPackageLogger {
void startProcessPackage();
void endProcessPackage();
}

View File

@@ -0,0 +1,304 @@
/*
* 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;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.jni.HybridData;
import com.facebook.react.bridge.CxxModuleWrapper;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.internal.turbomodule.core.TurboModuleManagerDelegate;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Provider;
public abstract class ReactPackageTurboModuleManagerDelegate extends TurboModuleManagerDelegate {
interface ModuleProvider {
@Nullable
NativeModule getModule(String moduleName);
}
private final List<ModuleProvider> mModuleProviders = new ArrayList<>();
private final Map<ModuleProvider, Map<String, ReactModuleInfo>> mPackageModuleInfos =
new HashMap<>();
private final boolean mShouldEnableLegacyModuleInterop =
ReactFeatureFlags.enableBridgelessArchitecture
&& ReactFeatureFlags.unstable_useTurboModuleInterop;
private final boolean mShouldRouteTurboModulesThroughLegacyModuleInterop =
mShouldEnableLegacyModuleInterop
&& ReactFeatureFlags.unstable_useTurboModuleInteropForAllTurboModules;
private final boolean mEnableTurboModuleSyncVoidMethods =
ReactFeatureFlags.unstable_enableTurboModuleSyncVoidMethods;
// Lazy Props
private List<ReactPackage> mPackages;
private ReactApplicationContext mReactContext;
protected ReactPackageTurboModuleManagerDelegate(
ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
super();
initialize(reactApplicationContext, packages);
}
protected ReactPackageTurboModuleManagerDelegate(
ReactApplicationContext reactApplicationContext,
List<ReactPackage> packages,
HybridData hybridData) {
super(hybridData);
initialize(reactApplicationContext, packages);
}
private void initialize(
ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
final ReactApplicationContext applicationContext = reactApplicationContext;
for (ReactPackage reactPackage : packages) {
if (reactPackage instanceof BaseReactPackage) {
final BaseReactPackage baseReactPackage = (BaseReactPackage) reactPackage;
final ModuleProvider moduleProvider =
moduleName -> baseReactPackage.getModule(moduleName, applicationContext);
mModuleProviders.add(moduleProvider);
mPackageModuleInfos.put(
moduleProvider, baseReactPackage.getReactModuleInfoProvider().getReactModuleInfos());
continue;
}
if (shouldSupportLegacyPackages() && reactPackage instanceof LazyReactPackage) {
// TODO(T145105887): Output warnings that LazyReactPackage was used
final LazyReactPackage lazyPkg = ((LazyReactPackage) reactPackage);
final List<ModuleSpec> moduleSpecs = lazyPkg.getNativeModules(reactApplicationContext);
final Map<String, Provider<? extends NativeModule>> moduleSpecProviderMap = new HashMap<>();
for (final ModuleSpec moduleSpec : moduleSpecs) {
moduleSpecProviderMap.put(moduleSpec.getName(), moduleSpec.getProvider());
}
final ModuleProvider moduleProvider =
moduleName -> {
Provider<? extends NativeModule> provider = moduleSpecProviderMap.get(moduleName);
return provider != null ? provider.get() : null;
};
mModuleProviders.add(moduleProvider);
mPackageModuleInfos.put(
moduleProvider, lazyPkg.getReactModuleInfoProvider().getReactModuleInfos());
continue;
}
if (shouldSupportLegacyPackages()) {
// TODO(T145105887): Output warnings that ReactPackage was used
final List<NativeModule> nativeModules =
reactPackage.createNativeModules(reactApplicationContext);
final Map<String, NativeModule> moduleMap = new HashMap<>();
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
for (final NativeModule module : nativeModules) {
final Class<? extends NativeModule> moduleClass = module.getClass();
final @Nullable ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
final String moduleName = reactModule != null ? reactModule.name() : module.getName();
final ReactModuleInfo moduleInfo =
reactModule != null
? new ReactModuleInfo(
moduleName,
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
true,
reactModule.isCxxModule(),
ReactModuleInfo.classIsTurboModule(moduleClass))
: new ReactModuleInfo(
moduleName,
moduleClass.getName(),
module.canOverrideExistingModule(),
true,
CxxModuleWrapper.class.isAssignableFrom(moduleClass),
ReactModuleInfo.classIsTurboModule(moduleClass));
reactModuleInfoMap.put(moduleName, moduleInfo);
moduleMap.put(moduleName, module);
}
final ModuleProvider moduleProvider = moduleMap::get;
mModuleProviders.add(moduleProvider);
mPackageModuleInfos.put(moduleProvider, reactModuleInfoMap);
}
}
}
@Override
public boolean unstable_shouldEnableLegacyModuleInterop() {
return mShouldEnableLegacyModuleInterop;
}
@Override
public boolean unstable_shouldRouteTurboModulesThroughLegacyModuleInterop() {
return mShouldRouteTurboModulesThroughLegacyModuleInterop;
}
public boolean unstable_enableSyncVoidMethods() {
return mEnableTurboModuleSyncVoidMethods;
}
@Nullable
@Override
public TurboModule getModule(String moduleName) {
NativeModule resolvedModule = null;
for (final ModuleProvider moduleProvider : mModuleProviders) {
try {
final ReactModuleInfo moduleInfo = mPackageModuleInfos.get(moduleProvider).get(moduleName);
if (moduleInfo != null
&& moduleInfo.isTurboModule()
&& (resolvedModule == null || moduleInfo.canOverrideExistingModule())) {
final NativeModule module = moduleProvider.getModule(moduleName);
if (module != null) {
resolvedModule = module;
}
}
} catch (IllegalArgumentException ex) {
// TODO T170570617: remove this catch statement and let exception bubble up
FLog.e(
ReactConstants.TAG,
ex,
"Caught exception while constructing module '%s'. This was previously ignored but will not be caught in the future.",
moduleName);
}
}
// Skip TurboModule-incompatible modules
boolean isLegacyModule = !(resolvedModule instanceof TurboModule);
if (isLegacyModule) {
return null;
}
return (TurboModule) resolvedModule;
}
@Override
public boolean unstable_isModuleRegistered(String moduleName) {
for (final ModuleProvider moduleProvider : mModuleProviders) {
final ReactModuleInfo moduleInfo = mPackageModuleInfos.get(moduleProvider).get(moduleName);
if (moduleInfo != null && moduleInfo.isTurboModule()) {
return true;
}
}
return false;
}
@Override
public boolean unstable_isLegacyModuleRegistered(String moduleName) {
for (final ModuleProvider moduleProvider : mModuleProviders) {
final ReactModuleInfo moduleInfo = mPackageModuleInfos.get(moduleProvider).get(moduleName);
if (moduleInfo != null && !moduleInfo.isTurboModule()) {
return true;
}
}
return false;
}
@Nullable
@Override
public NativeModule getLegacyModule(String moduleName) {
if (!unstable_shouldEnableLegacyModuleInterop()) {
return null;
}
NativeModule resolvedModule = null;
for (final ModuleProvider moduleProvider : mModuleProviders) {
try {
final ReactModuleInfo moduleInfo = mPackageModuleInfos.get(moduleProvider).get(moduleName);
if (moduleInfo != null
&& !moduleInfo.isTurboModule()
&& (resolvedModule == null || moduleInfo.canOverrideExistingModule())) {
final NativeModule module = moduleProvider.getModule(moduleName);
if (module != null) {
resolvedModule = module;
}
}
} catch (IllegalArgumentException ex) {
// TODO T170570617: remove this catch statement and let exception bubble up
FLog.e(
ReactConstants.TAG,
ex,
"Caught exception while constructing module '%s'. This was previously ignored but will not be caught in the future.",
moduleName);
}
}
// Skip TurboModule-compatible modules
boolean isLegacyModule = !(resolvedModule instanceof TurboModule);
if (!isLegacyModule) {
return null;
}
return resolvedModule;
}
@Override
public List<String> getEagerInitModuleNames() {
List<String> moduleNames = new ArrayList<>();
for (final ModuleProvider moduleProvider : mModuleProviders) {
for (final ReactModuleInfo moduleInfo : mPackageModuleInfos.get(moduleProvider).values()) {
if (moduleInfo.isTurboModule() && moduleInfo.needsEagerInit()) {
moduleNames.add(moduleInfo.name());
}
}
}
return moduleNames;
}
private boolean shouldSupportLegacyPackages() {
return unstable_shouldEnableLegacyModuleInterop();
}
public abstract static class Builder {
private @Nullable List<ReactPackage> mPackages;
private @Nullable ReactApplicationContext mContext;
public Builder setPackages(List<ReactPackage> packages) {
mPackages = new ArrayList<>(packages);
return this;
}
public Builder setReactApplicationContext(ReactApplicationContext context) {
mContext = context;
return this;
}
protected abstract ReactPackageTurboModuleManagerDelegate build(
ReactApplicationContext context, List<ReactPackage> packages);
public ReactPackageTurboModuleManagerDelegate build() {
Assertions.assertNotNull(
mContext,
"The ReactApplicationContext must be provided to create ReactPackageTurboModuleManagerDelegate");
Assertions.assertNotNull(
mPackages,
"A set of ReactPackages must be provided to create ReactPackageTurboModuleManagerDelegate");
return build(mContext, mPackages);
}
}
}

View File

@@ -0,0 +1,14 @@
/*
* 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;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
/** This will eventually replace {@link LazyReactPackage} when TurboModules are finally done. */
@DeprecatedInNewArchitecture(message = "Use BaseReactPackage instead")
public abstract class TurboReactPackage extends BaseReactPackage {}

View File

@@ -0,0 +1,27 @@
/*
* 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;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collection;
public interface ViewManagerOnDemandReactPackage {
/**
* Provides a list of names of ViewManagers with which these modules can be accessed from JS.
* Typically, this is ViewManager.getName().
*/
Collection<String> getViewManagerNames(ReactApplicationContext reactContext);
/**
* Creates and returns a ViewManager with a specific name {@param viewManagerName}. It's up to an
* implementing package how to interpret the name.
*/
@Nullable
ViewManager createViewManager(ReactApplicationContext reactContext, String viewManagerName);
}

View File

@@ -0,0 +1,56 @@
/*
* 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.animated;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Animated node that plays a role of value aggregator. It takes two or more value nodes as an input
* and outputs a sum of values outputted by those nodes.
*/
/*package*/ class AdditionAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int[] mInputNodes;
public AdditionAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
ReadableArray inputNodes = config.getArray("input");
mInputNodes = new int[inputNodes.size()];
for (int i = 0; i < mInputNodes.length; i++) {
mInputNodes[i] = inputNodes.getInt(i);
}
}
@Override
public void update() {
mValue = 0;
for (int i = 0; i < mInputNodes.length; i++) {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
mValue += ((ValueAnimatedNode) animatedNode).getValue();
} else {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.Add node");
}
}
}
@Override
public String prettyPrint() {
return "AdditionAnimatedNode["
+ mTag
+ "]: input nodes: "
+ (mInputNodes != null ? mInputNodes.toString() : "null")
+ " - super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import java.util.ArrayList;
import java.util.List;
/** Base class for all Animated.js library node types that can be created on the "native" side. */
/*package*/ @Nullsafe(Nullsafe.Mode.LOCAL)
abstract class AnimatedNode {
public static final int INITIAL_BFS_COLOR = 0;
private static final int DEFAULT_ANIMATED_NODE_CHILD_COUNT = 1;
/*package*/ @Nullable List<AnimatedNode> mChildren; /* lazy-initialized when a child is added */
/*package*/ int mActiveIncomingNodes = 0;
/*package*/ int mBFSColor = INITIAL_BFS_COLOR;
/*package*/ int mTag = -1;
public final void addChild(AnimatedNode child) {
if (mChildren == null) {
mChildren = new ArrayList<>(DEFAULT_ANIMATED_NODE_CHILD_COUNT);
}
Assertions.assertNotNull(mChildren).add(child);
child.onAttachedToNode(this);
}
public final void removeChild(AnimatedNode child) {
if (mChildren == null) {
return;
}
child.onDetachedFromNode(this);
mChildren.remove(child);
}
/**
* Subclasses may want to override this method in order to store a reference to the parent of a
* given node that can then be used to calculate current node's value in {@link #update}. In that
* case it is important to also override {@link #onDetachedFromNode} to clear that reference once
* current node gets detached.
*/
public void onAttachedToNode(AnimatedNode parent) {}
/** See {@link #onAttachedToNode} */
public void onDetachedFromNode(AnimatedNode parent) {}
/**
* This method will be run on each node at most once every repetition of the animation loop. It
* will be executed on a node only when all the node's parent has already been updated. Therefore
* it can be used to calculate node's value.
*/
public void update() {}
/**
* Pretty-printer for the AnimatedNode. Only called in production pre-crash for debug diagnostics.
*/
public abstract String prettyPrint();
public String prettyPrintWithChildren() {
String children = "";
if (mChildren != null && mChildren.size() > 0) {
for (AnimatedNode child : mChildren) {
children += " " + child.mTag;
}
}
return prettyPrint() + (children.length() > 0 ? " children: " + children : "");
}
}

View File

@@ -0,0 +1,13 @@
/*
* 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.animated;
/** Interface used to listen to {@link ValueAnimatedNode} updates. */
interface AnimatedNodeValueListener {
void onValueUpdate(double value);
}

View File

@@ -0,0 +1,15 @@
/*
* 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.animated;
import com.facebook.react.bridge.ReadableMap;
/** Indicates that AnimatedNode is able to receive native config updates. */
interface AnimatedNodeWithUpdateableConfig {
void onUpdateConfig(ReadableMap config);
}

View File

@@ -0,0 +1,41 @@
/*
* 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.animated;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableMap;
/**
* Base class for different types of animation drivers. Can be used to implement simple time-based
* animations as well as spring based animations.
*/
/*package*/ abstract class AnimationDriver {
/*package*/ boolean mHasFinished = false;
/*package*/ ValueAnimatedNode mAnimatedValue;
/*package*/ Callback mEndCallback;
/*package*/ int mId;
/**
* This method gets called in the main animation loop with a frame time passed down from the
* android choreographer callback.
*/
public abstract void runAnimationStep(long frameTimeNanos);
/**
* This method will get called when some of the configuration gets updated while the animation is
* running. In that case animation should restart keeping its internal state to provide a smooth
* transition. E.g. in case of a spring animation we want to keep the current value and speed and
* start animating with the new properties (different destination or spring settings)
*/
public void resetConfig(ReadableMap config) {
throw new JSApplicationCausedNativeException(
"Animation config for " + getClass().getSimpleName() + " cannot be reset");
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.animated;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.views.view.ColorUtil;
/** Animated node that represents a color. */
/*package*/ class ColorAnimatedNode extends AnimatedNode
implements AnimatedNodeWithUpdateableConfig {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final ReactApplicationContext mReactApplicationContext;
private int mRNodeId;
private int mGNodeId;
private int mBNodeId;
private int mANodeId;
private ReadableMap mNativeColor;
private boolean mNativeColorApplied;
public ColorAnimatedNode(
ReadableMap config,
NativeAnimatedNodesManager nativeAnimatedNodesManager,
ReactApplicationContext reactApplicationContext) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
mReactApplicationContext = reactApplicationContext;
onUpdateConfig(config);
}
public int getColor() {
tryApplyNativeColor();
ValueAnimatedNode rNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mRNodeId);
ValueAnimatedNode gNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mGNodeId);
ValueAnimatedNode bNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mBNodeId);
ValueAnimatedNode aNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mANodeId);
double r = rNode.getValue();
double g = gNode.getValue();
double b = bNode.getValue();
double a = aNode.getValue();
return ColorUtil.normalize(r, g, b, a);
}
public void onUpdateConfig(ReadableMap config) {
mRNodeId = config.getInt("r");
mGNodeId = config.getInt("g");
mBNodeId = config.getInt("b");
mANodeId = config.getInt("a");
mNativeColor = config.getMap("nativeColor");
mNativeColorApplied = false;
tryApplyNativeColor();
}
@Override
public String prettyPrint() {
return "ColorAnimatedNode["
+ mTag
+ "]: r: "
+ mRNodeId
+ " g: "
+ mGNodeId
+ " b: "
+ mBNodeId
+ " a: "
+ mANodeId;
}
private void tryApplyNativeColor() {
if (mNativeColor == null || mNativeColorApplied) {
return;
}
Context context = getContext();
if (context == null) {
return;
}
int color = ColorPropConverter.getColor(mNativeColor, context);
ValueAnimatedNode rNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mRNodeId);
ValueAnimatedNode gNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mGNodeId);
ValueAnimatedNode bNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mBNodeId);
ValueAnimatedNode aNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mANodeId);
rNode.mValue = Color.red(color);
gNode.mValue = Color.green(color);
bNode.mValue = Color.blue(color);
aNode.mValue = Color.alpha(color) / 255.0;
mNativeColorApplied = true;
}
private Context getContext() {
Context context = mReactApplicationContext.getCurrentActivity();
if (context != null) {
return context;
}
// There are cases where the activity may not exist (such as for VRShell panel apps). In this
// case we will search for a view associated with a PropsAnimatedNode to get the context.
return getContextHelper(this);
}
private static Context getContextHelper(AnimatedNode node) {
// Search children depth-first until we get to a PropsAnimatedNode, from which we can
// get the view and its context
if (node.mChildren != null) {
for (AnimatedNode child : node.mChildren) {
if (child instanceof PropsAnimatedNode) {
View view = ((PropsAnimatedNode) child).getConnectedView();
return view != null ? view.getContext() : null;
} else {
return getContextHelper(child);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.animated;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReadableMap;
/**
* Implementation of {@link AnimationDriver} providing support for decay animations. The
* implementation is copied from the JS version in {@code AnimatedImplementation.js}.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
class DecayAnimation extends AnimationDriver {
private final double mVelocity;
private double mDeceleration;
private long mStartFrameTimeMillis;
private double mFromValue;
private double mLastValue;
private int mIterations;
private int mCurrentLoop;
public DecayAnimation(ReadableMap config) {
mVelocity = config.getDouble("velocity"); // initial velocity
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
mDeceleration = config.getDouble("deceleration");
mIterations = config.hasKey("iterations") ? config.getInt("iterations") : 1;
mCurrentLoop = 1;
mHasFinished = mIterations == 0;
mStartFrameTimeMillis = -1;
mFromValue = 0;
mLastValue = 0;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
long frameTimeMillis = frameTimeNanos / 1000000;
if (mStartFrameTimeMillis == -1) {
// since this is the first animation step, consider the start to be on the previous frame
mStartFrameTimeMillis = frameTimeMillis - 16;
if (mFromValue == mLastValue) { // first iteration, assign mFromValue based on mAnimatedValue
mFromValue = mAnimatedValue.mValue;
} else { // not the first iteration, reset mAnimatedValue based on mFromValue
mAnimatedValue.mValue = mFromValue;
}
mLastValue = mAnimatedValue.mValue;
}
final double value =
mFromValue
+ (mVelocity / (1 - mDeceleration))
* (1 - Math.exp(-(1 - mDeceleration) * (frameTimeMillis - mStartFrameTimeMillis)));
if (Math.abs(mLastValue - value) < 0.1) {
if (mIterations == -1 || mCurrentLoop < mIterations) { // looping animation, return to start
// set mStartFrameTimeMillis to -1 to reset instance variables on the next runAnimationStep
mStartFrameTimeMillis = -1;
mCurrentLoop++;
} else { // animation has completed
mHasFinished = true;
return;
}
}
mLastValue = value;
mAnimatedValue.mValue = value;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.animated;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableMap;
/*package*/ @Nullsafe(Nullsafe.Mode.LOCAL)
class DiffClampAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int mInputNodeTag;
private final double mMin;
private final double mMax;
private double mLastValue;
public DiffClampAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
mInputNodeTag = config.getInt("input");
mMin = config.getDouble("min");
mMax = config.getDouble("max");
mValue = mLastValue = 0;
}
@Override
public void update() {
double value = getInputNodeValue();
double diff = value - mLastValue;
mLastValue = value;
mValue = Math.min(Math.max(mValue + diff, mMin), mMax);
}
private double getInputNodeValue() {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodeTag);
if (animatedNode == null || !(animatedNode instanceof ValueAnimatedNode)) {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.DiffClamp node");
}
return ((ValueAnimatedNode) animatedNode).getValue();
}
public String prettyPrint() {
return "DiffClampAnimatedNode["
+ mTag
+ "]: InputNodeTag: "
+ mInputNodeTag
+ " min: "
+ mMin
+ " max: "
+ mMax
+ " lastValue: "
+ mLastValue
+ " super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.animated;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Animated node which takes two or more value node as an input and outputs an in-order division of
* their values.
*/
/*package*/ class DivisionAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int[] mInputNodes;
public DivisionAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
ReadableArray inputNodes = config.getArray("input");
mInputNodes = new int[inputNodes.size()];
for (int i = 0; i < mInputNodes.length; i++) {
mInputNodes[i] = inputNodes.getInt(i);
}
}
@Override
public void update() {
for (int i = 0; i < mInputNodes.length; i++) {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
double value = ((ValueAnimatedNode) animatedNode).getValue();
if (i == 0) {
mValue = value;
continue;
}
if (value == 0) {
throw new JSApplicationCausedNativeException(
"Detected a division by zero in Animated.divide node with Animated ID " + mTag);
}
mValue /= value;
} else {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.divide node with Animated ID " + mTag);
}
}
}
@Override
public String prettyPrint() {
return "DivisionAnimatedNode["
+ mTag
+ "]: input nodes: "
+ (mInputNodes != null ? mInputNodes.toString() : "null")
+ " - super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UnexpectedNativeTypeException;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.EventCategoryDef;
import com.facebook.react.uimanager.events.RCTModernEventEmitter;
import com.facebook.react.uimanager.events.TouchEvent;
import java.util.List;
/** Handles updating a {@link ValueAnimatedNode} when an event gets dispatched. */
/* package */ class EventAnimationDriver implements RCTModernEventEmitter {
private List<String> mEventPath;
/* package */ ValueAnimatedNode mValueNode;
/* package */ String mEventName;
/* package */ int mViewTag;
public EventAnimationDriver(
String eventName, int viewTag, List<String> eventPath, ValueAnimatedNode valueNode) {
mEventName = eventName;
mViewTag = viewTag;
mEventPath = eventPath;
mValueNode = valueNode;
}
@Override
public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap event) {
receiveEvent(-1, targetReactTag, eventName, event);
}
@Override
public void receiveEvent(
int surfaceId, int targetTag, String eventName, @Nullable WritableMap event) {
// We assume this event can't be coalesced. `customCoalesceKey` has no meaning in Fabric.
receiveEvent(surfaceId, targetTag, eventName, false, 0, event, EventCategoryDef.UNSPECIFIED);
}
@Override
public void receiveTouches(
String eventName, WritableArray touches, WritableArray changedIndices) {
throw new UnsupportedOperationException(
"receiveTouches is not support by native animated events");
}
@Override
public void receiveTouches(TouchEvent touchEvent) {
throw new UnsupportedOperationException(
"receiveTouches is not support by native animated events");
}
@Override
public void receiveEvent(
int surfaceId,
int targetTag,
String eventName,
boolean canCoalesceEvent,
int customCoalesceKey,
@Nullable WritableMap event,
@EventCategoryDef int category) {
if (event == null) {
throw new IllegalArgumentException("Native animated events must have event data.");
}
// Get the new value for the node by looking into the event map using the provided event path.
ReadableMap currMap = event;
ReadableArray currArray = null;
for (int i = 0; i < mEventPath.size() - 1; i++) {
if (currMap != null) {
String key = mEventPath.get(i);
ReadableType keyType = currMap.getType(key);
if (keyType == ReadableType.Map) {
currMap = currMap.getMap(key);
currArray = null;
} else if (keyType == ReadableType.Array) {
currArray = currMap.getArray(key);
currMap = null;
} else {
throw new UnexpectedNativeTypeException(
"Unexpected type " + keyType + " for key '" + key + "'");
}
} else {
int index = Integer.parseInt(mEventPath.get(i));
ReadableType keyType = currArray.getType(index);
if (keyType == ReadableType.Map) {
currMap = currArray.getMap(index);
currArray = null;
} else if (keyType == ReadableType.Array) {
currArray = currArray.getArray(index);
currMap = null;
} else {
throw new UnexpectedNativeTypeException(
"Unexpected type " + keyType + " for index '" + index + "'");
}
}
}
String lastKey = mEventPath.get(mEventPath.size() - 1);
if (currMap != null) {
mValueNode.mValue = currMap.getDouble(lastKey);
} else {
int lastIndex = Integer.parseInt(lastKey);
mValueNode.mValue = currArray.getDouble(lastIndex);
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.animated;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.build.ReactBuildConfig;
/**
* Implementation of {@link AnimationDriver} which provides a support for simple time-based
* animations that are pre-calculate on the JS side. For each animation frame JS provides a value
* from 0 to 1 that indicates a progress of the animation at that frame.
*/
class FrameBasedAnimationDriver extends AnimationDriver {
// 60FPS
private static final double FRAME_TIME_MILLIS = 1000d / 60d;
private long mStartFrameTimeNanos;
private double[] mFrames;
private double mToValue;
private double mFromValue;
private int mIterations;
private int mCurrentLoop;
FrameBasedAnimationDriver(ReadableMap config) {
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
ReadableArray frames = config.getArray("frames");
int numberOfFrames = frames.size();
if (mFrames == null || mFrames.length != numberOfFrames) {
mFrames = new double[numberOfFrames];
}
for (int i = 0; i < numberOfFrames; i++) {
mFrames[i] = frames.getDouble(i);
}
if (config.hasKey("toValue")) {
mToValue = config.getType("toValue") == ReadableType.Number ? config.getDouble("toValue") : 0;
} else {
mToValue = 0;
}
if (config.hasKey("iterations")) {
mIterations =
config.getType("iterations") == ReadableType.Number ? config.getInt("iterations") : 1;
} else {
mIterations = 1;
}
mCurrentLoop = 1;
mHasFinished = mIterations == 0;
mStartFrameTimeNanos = -1;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
if (mStartFrameTimeNanos < 0) {
mStartFrameTimeNanos = frameTimeNanos;
if (mCurrentLoop == 1) {
// initiate start value when animation runs for the first time
mFromValue = mAnimatedValue.mValue;
}
}
long timeFromStartMillis = (frameTimeNanos - mStartFrameTimeNanos) / 1000000;
int frameIndex = (int) Math.round(timeFromStartMillis / FRAME_TIME_MILLIS);
if (frameIndex < 0) {
String message =
"Calculated frame index should never be lower than 0. Called with frameTimeNanos "
+ frameTimeNanos
+ " and mStartFrameTimeNanos "
+ mStartFrameTimeNanos;
if (ReactBuildConfig.DEBUG) {
throw new IllegalStateException(message);
} else {
FLog.w(ReactConstants.TAG, message);
return;
}
} else if (mHasFinished) {
// nothing to do here
return;
}
double nextValue;
if (frameIndex >= mFrames.length - 1) {
nextValue = mToValue;
if (mIterations == -1 || mCurrentLoop < mIterations) { // looping animation, return to start
mStartFrameTimeNanos = -1;
mCurrentLoop++;
} else { // animation has completed, no more frames left
mHasFinished = true;
}
} else {
nextValue = mFromValue + mFrames[frameIndex] * (mToValue - mFromValue);
}
mAnimatedValue.mValue = nextValue;
}
}

View File

@@ -0,0 +1,311 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Animated node that corresponds to {@code AnimatedInterpolation} from AnimatedImplementation.js.
*
* <p>Currently only a linear interpolation is supported on an input range of an arbitrary size.
*/
/*package*/ class InterpolationAnimatedNode extends ValueAnimatedNode {
public static final String EXTRAPOLATE_TYPE_IDENTITY = "identity";
public static final String EXTRAPOLATE_TYPE_CLAMP = "clamp";
public static final String EXTRAPOLATE_TYPE_EXTEND = "extend";
private static final Pattern sNumericPattern =
Pattern.compile("[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?");
private static final String COLOR_OUTPUT_TYPE = "color";
private static double[] fromDoubleArray(ReadableArray ary) {
double[] res = new double[ary.size()];
for (int i = 0; i < res.length; i++) {
res[i] = ary.getDouble(i);
}
return res;
}
private static int[] fromIntArray(ReadableArray ary) {
int[] res = new int[ary.size()];
for (int i = 0; i < res.length; i++) {
res[i] = ary.getInt(i);
}
return res;
}
private static double[][] fromStringPattern(ReadableArray array) {
int size = array.size();
double[][] outputRange = new double[size][];
// Match the first pattern into a List, since we don't know its length yet
Matcher m = sNumericPattern.matcher(array.getString(0));
List<Double> firstOutputRange = new ArrayList<>();
while (m.find()) {
firstOutputRange.add(Double.parseDouble(m.group()));
}
double[] firstOutputRangeArr = new double[firstOutputRange.size()];
for (int i = 0; i < firstOutputRange.size(); i++) {
firstOutputRangeArr[i] = firstOutputRange.get(i).doubleValue();
}
outputRange[0] = firstOutputRangeArr;
for (int i = 1; i < size; i++) {
double[] outputArr = new double[firstOutputRangeArr.length];
int j = 0;
m = sNumericPattern.matcher(array.getString(i));
while (m.find() && j < firstOutputRangeArr.length) {
outputArr[j++] = Double.parseDouble(m.group());
}
outputRange[i] = outputArr;
}
return outputRange;
}
private static double interpolate(
double value,
double inputMin,
double inputMax,
double outputMin,
double outputMax,
String extrapolateLeft,
String extrapolateRight) {
double result = value;
// Extrapolate
if (result < inputMin) {
switch (extrapolateLeft) {
case EXTRAPOLATE_TYPE_IDENTITY:
return result;
case EXTRAPOLATE_TYPE_CLAMP:
result = inputMin;
break;
case EXTRAPOLATE_TYPE_EXTEND:
break;
default:
throw new JSApplicationIllegalArgumentException(
"Invalid extrapolation type " + extrapolateLeft + "for left extrapolation");
}
}
if (result > inputMax) {
switch (extrapolateRight) {
case EXTRAPOLATE_TYPE_IDENTITY:
return result;
case EXTRAPOLATE_TYPE_CLAMP:
result = inputMax;
break;
case EXTRAPOLATE_TYPE_EXTEND:
break;
default:
throw new JSApplicationIllegalArgumentException(
"Invalid extrapolation type " + extrapolateRight + "for right extrapolation");
}
}
if (outputMin == outputMax) {
return outputMin;
}
if (inputMin == inputMax) {
if (value <= inputMin) {
return outputMin;
}
return outputMax;
}
return outputMin + (outputMax - outputMin) * (result - inputMin) / (inputMax - inputMin);
}
/*package*/ static double interpolate(
double value,
double[] inputRange,
double[] outputRange,
String extrapolateLeft,
String extrapolateRight) {
int rangeIndex = findRangeIndex(value, inputRange);
return interpolate(
value,
inputRange[rangeIndex],
inputRange[rangeIndex + 1],
outputRange[rangeIndex],
outputRange[rangeIndex + 1],
extrapolateLeft,
extrapolateRight);
}
/*package*/ static int interpolateColor(double value, double[] inputRange, int[] outputRange) {
int rangeIndex = findRangeIndex(value, inputRange);
int outputMin = outputRange[rangeIndex];
int outputMax = outputRange[rangeIndex + 1];
if (outputMin == outputMax) {
return outputMin;
}
double inputMin = inputRange[rangeIndex];
double inputMax = inputRange[rangeIndex + 1];
if (inputMin == inputMax) {
if (value <= inputMin) {
return outputMin;
}
return outputMax;
}
double ratio = (value - inputMin) / (inputMax - inputMin);
return ColorUtils.blendARGB(outputMin, outputMax, (float) ratio);
}
/*package*/ static String interpolateString(
String pattern,
double value,
double[] inputRange,
double[][] outputRange,
String extrapolateLeft,
String extrapolateRight) {
int rangeIndex = findRangeIndex(value, inputRange);
StringBuffer sb = new StringBuffer(pattern.length());
Matcher m = sNumericPattern.matcher(pattern);
int i = 0;
while (m.find() && i < outputRange[rangeIndex].length) {
double val =
interpolate(
value,
inputRange[rangeIndex],
inputRange[rangeIndex + 1],
outputRange[rangeIndex][i],
outputRange[rangeIndex + 1][i],
extrapolateLeft,
extrapolateRight);
int intVal = (int) val;
m.appendReplacement(sb, intVal != val ? Double.toString(val) : Integer.toString(intVal));
i++;
}
m.appendTail(sb);
return sb.toString();
}
private static int findRangeIndex(double value, double[] ranges) {
int index;
for (index = 1; index < ranges.length - 1; index++) {
if (ranges[index] >= value) {
break;
}
}
return index - 1;
}
private enum OutputType {
Number,
Color,
String,
}
private final double mInputRange[];
private final Object mOutputRange;
private final OutputType mOutputType;
private final @Nullable String mPattern;
private final String mExtrapolateLeft;
private final String mExtrapolateRight;
private @Nullable ValueAnimatedNode mParent;
private Object mObjectValue;
public InterpolationAnimatedNode(ReadableMap config) {
mInputRange = fromDoubleArray(config.getArray("inputRange"));
ReadableArray output = config.getArray("outputRange");
if (COLOR_OUTPUT_TYPE.equals(config.getString("outputType"))) {
mOutputType = OutputType.Color;
mOutputRange = fromIntArray(output);
mPattern = null;
} else if (output.getType(0) == ReadableType.String) {
mOutputType = OutputType.String;
mOutputRange = fromStringPattern(output);
mPattern = output.getString(0);
} else {
mOutputType = OutputType.Number;
mOutputRange = fromDoubleArray(output);
mPattern = null;
}
mExtrapolateLeft = config.getString("extrapolateLeft");
mExtrapolateRight = config.getString("extrapolateRight");
}
@Override
public void onAttachedToNode(AnimatedNode parent) {
if (mParent != null) {
throw new IllegalStateException("Parent already attached");
}
if (!(parent instanceof ValueAnimatedNode)) {
throw new IllegalArgumentException("Parent is of an invalid type");
}
mParent = (ValueAnimatedNode) parent;
}
@Override
public void onDetachedFromNode(AnimatedNode parent) {
if (parent != mParent) {
throw new IllegalArgumentException("Invalid parent node provided");
}
mParent = null;
}
@Override
public void update() {
if (mParent == null) {
// The graph is in the middle of being created, just skip this unattached node.
return;
}
double value = mParent.getValue();
switch (mOutputType) {
case Number:
mValue =
interpolate(
value, mInputRange, (double[]) mOutputRange, mExtrapolateLeft, mExtrapolateRight);
break;
case Color:
mObjectValue = Integer.valueOf(interpolateColor(value, mInputRange, (int[]) mOutputRange));
break;
case String:
mObjectValue =
interpolateString(
mPattern,
value,
mInputRange,
(double[][]) mOutputRange,
mExtrapolateLeft,
mExtrapolateRight);
break;
}
}
@Override
public Object getAnimatedObject() {
return mObjectValue;
}
@Override
public String prettyPrint() {
return "InterpolationAnimatedNode[" + mTag + "] super: " + super.prettyPrint();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.animated;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableMap;
/*package*/ @Nullsafe(Nullsafe.Mode.LOCAL)
class ModulusAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int mInputNode;
private final double mModulus;
public ModulusAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
mInputNode = config.getInt("input");
mModulus = config.getDouble("modulus");
}
@Override
public void update() {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNode);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
double value = ((ValueAnimatedNode) animatedNode).getValue();
mValue = (value % mModulus + mModulus) % mModulus;
} else {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.modulus node");
}
}
public String prettyPrint() {
return "NativeAnimatedNodesManager["
+ mTag
+ "] inputNode: "
+ mInputNode
+ " modulus: "
+ mModulus
+ " super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.animated;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Animated node which takes two or more value node as an input and outputs a product of their
* values
*/
/*package*/ class MultiplicationAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int[] mInputNodes;
public MultiplicationAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
ReadableArray inputNodes = config.getArray("input");
mInputNodes = new int[inputNodes.size()];
for (int i = 0; i < mInputNodes.length; i++) {
mInputNodes[i] = inputNodes.getInt(i);
}
}
@Override
public void update() {
mValue = 1;
for (int i = 0; i < mInputNodes.length; i++) {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
mValue *= ((ValueAnimatedNode) animatedNode).getValue();
} else {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.multiply node");
}
}
}
@Override
public String prettyPrint() {
return "MultiplicationAnimatedNode["
+ mTag
+ "]: input nodes: "
+ (mInputNodes != null ? mInputNodes.toString() : "null")
+ " - super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,853 @@
/*
* 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.animated;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.EventDispatcherListener;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Queue;
/**
* This is the main class that coordinates how native animated JS implementation drives UI changes.
*
* <p>It implements a management interface for animated nodes graph as well as implements a graph
* traversal algorithm that is run for each animation frame.
*
* <p>For each animation frame we visit animated nodes that might've been updated as well as their
* children that may use parent's values to update themselves. At the end of the traversal algorithm
* we expect to reach a special type of the node: PropsAnimatedNode that is then responsible for
* calculating property map which can be sent to native view hierarchy to update the view.
*
* <p>IMPORTANT: This class should be accessed only from the UI Thread
*/
public class NativeAnimatedNodesManager implements EventDispatcherListener {
private static final String TAG = "NativeAnimatedNodesManager";
private final SparseArray<AnimatedNode> mAnimatedNodes = new SparseArray<>();
private final SparseArray<AnimationDriver> mActiveAnimations = new SparseArray<>();
private final SparseArray<AnimatedNode> mUpdatedNodes = new SparseArray<>();
// List of event animation drivers for an event on view.
// There may be multiple drivers for the same event and view.
private final List<EventAnimationDriver> mEventDrivers = new ArrayList<>();
private final ReactApplicationContext mReactApplicationContext;
private int mAnimatedGraphBFSColor = 0;
// Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`.
private final List<AnimatedNode> mRunUpdateNodeList = new LinkedList<>();
private boolean mEventListenerInitializedForFabric = false;
private boolean mEventListenerInitializedForNonFabric = false;
private boolean mWarnedAboutGraphTraversal = false;
public NativeAnimatedNodesManager(ReactApplicationContext reactApplicationContext) {
mReactApplicationContext = reactApplicationContext;
}
/**
* Initialize event listeners for Fabric UIManager or non-Fabric UIManager, exactly once. Once
* Fabric is the only UIManager, this logic can be simplified. This is expected to only be called
* from the native module thread.
*
* @param uiManagerType
*/
public void initializeEventListenerForUIManagerType(@UIManagerType final int uiManagerType) {
if (uiManagerType == UIManagerType.FABRIC
? mEventListenerInitializedForFabric
: mEventListenerInitializedForNonFabric) {
return;
}
UIManager uiManager = UIManagerHelper.getUIManager(mReactApplicationContext, uiManagerType);
if (uiManager != null) {
uiManager.<EventDispatcher>getEventDispatcher().addListener(this);
if (uiManagerType == UIManagerType.FABRIC) {
mEventListenerInitializedForFabric = true;
} else {
mEventListenerInitializedForNonFabric = true;
}
}
}
@Nullable
public AnimatedNode getNodeById(int id) {
return mAnimatedNodes.get(id);
}
public boolean hasActiveAnimations() {
return mActiveAnimations.size() > 0 || mUpdatedNodes.size() > 0;
}
@UiThread
public void createAnimatedNode(int tag, ReadableMap config) {
if (mAnimatedNodes.get(tag) != null) {
throw new JSApplicationIllegalArgumentException(
"createAnimatedNode: Animated node [" + tag + "] already exists");
}
String type = config.getString("type");
final AnimatedNode node;
if ("style".equals(type)) {
node = new StyleAnimatedNode(config, this);
} else if ("value".equals(type)) {
node = new ValueAnimatedNode(config);
} else if ("color".equals(type)) {
node = new ColorAnimatedNode(config, this, mReactApplicationContext);
} else if ("props".equals(type)) {
node = new PropsAnimatedNode(config, this);
} else if ("interpolation".equals(type)) {
node = new InterpolationAnimatedNode(config);
} else if ("addition".equals(type)) {
node = new AdditionAnimatedNode(config, this);
} else if ("subtraction".equals(type)) {
node = new SubtractionAnimatedNode(config, this);
} else if ("division".equals(type)) {
node = new DivisionAnimatedNode(config, this);
} else if ("multiplication".equals(type)) {
node = new MultiplicationAnimatedNode(config, this);
} else if ("modulus".equals(type)) {
node = new ModulusAnimatedNode(config, this);
} else if ("diffclamp".equals(type)) {
node = new DiffClampAnimatedNode(config, this);
} else if ("transform".equals(type)) {
node = new TransformAnimatedNode(config, this);
} else if ("tracking".equals(type)) {
node = new TrackingAnimatedNode(config, this);
} else if ("object".equals(type)) {
node = new ObjectAnimatedNode(config, this);
} else {
throw new JSApplicationIllegalArgumentException("Unsupported node type: " + type);
}
node.mTag = tag;
mAnimatedNodes.put(tag, node);
mUpdatedNodes.put(tag, node);
}
@UiThread
public void updateAnimatedNodeConfig(int tag, ReadableMap config) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null) {
throw new JSApplicationIllegalArgumentException(
"updateAnimatedNode: Animated node [" + tag + "] does not exist");
}
if (node instanceof AnimatedNodeWithUpdateableConfig) {
stopAnimationsForNode(node);
((AnimatedNodeWithUpdateableConfig) node).onUpdateConfig(config);
mUpdatedNodes.put(tag, node);
}
}
@UiThread
public void dropAnimatedNode(int tag) {
mAnimatedNodes.remove(tag);
mUpdatedNodes.remove(tag);
}
@UiThread
public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener listener) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"startListeningToAnimatedNodeValue: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
((ValueAnimatedNode) node).setValueListener(listener);
}
@UiThread
public void stopListeningToAnimatedNodeValue(int tag) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"startListeningToAnimatedNodeValue: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
((ValueAnimatedNode) node).setValueListener(null);
}
@UiThread
public void setAnimatedNodeValue(int tag, double value) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"setAnimatedNodeValue: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
stopAnimationsForNode(node);
((ValueAnimatedNode) node).mValue = value;
mUpdatedNodes.put(tag, node);
}
@UiThread
public void setAnimatedNodeOffset(int tag, double offset) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"setAnimatedNodeOffset: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
((ValueAnimatedNode) node).mOffset = offset;
mUpdatedNodes.put(tag, node);
}
@UiThread
public void flattenAnimatedNodeOffset(int tag) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"flattenAnimatedNodeOffset: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
((ValueAnimatedNode) node).flattenOffset();
}
@UiThread
public void extractAnimatedNodeOffset(int tag) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"extractAnimatedNodeOffset: Animated node ["
+ tag
+ "] does not exist, or is not a 'value' node");
}
((ValueAnimatedNode) node).extractOffset();
}
@UiThread
public void startAnimatingNode(
int animationId, int animatedNodeTag, ReadableMap animationConfig, Callback endCallback) {
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
if (node == null) {
throw new JSApplicationIllegalArgumentException(
"startAnimatingNode: Animated node [" + animatedNodeTag + "] does not exist");
}
if (!(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"startAnimatingNode: Animated node ["
+ animatedNodeTag
+ "] should be of type "
+ ValueAnimatedNode.class.getName());
}
final AnimationDriver existingDriver = mActiveAnimations.get(animationId);
if (existingDriver != null) {
// animation with the given ID is already running, we need to update its configuration instead
// of spawning a new one
existingDriver.resetConfig(animationConfig);
return;
}
String type = animationConfig.getString("type");
final AnimationDriver animation;
if ("frames".equals(type)) {
animation = new FrameBasedAnimationDriver(animationConfig);
} else if ("spring".equals(type)) {
animation = new SpringAnimation(animationConfig);
} else if ("decay".equals(type)) {
animation = new DecayAnimation(animationConfig);
} else {
throw new JSApplicationIllegalArgumentException(
"startAnimatingNode: Unsupported animation type [" + animatedNodeTag + "]: " + type);
}
animation.mId = animationId;
animation.mEndCallback = endCallback;
animation.mAnimatedValue = (ValueAnimatedNode) node;
mActiveAnimations.put(animationId, animation);
}
@UiThread
private void stopAnimationsForNode(AnimatedNode animatedNode) {
// in most of the cases there should never be more than a few active animations running at the
// same time. Therefore it does not make much sense to create an animationId -> animation
// object map that would require additional memory just to support the use-case of stopping
// an animation
WritableArray events = null;
for (int i = 0; i < mActiveAnimations.size(); i++) {
AnimationDriver animation = mActiveAnimations.valueAt(i);
if (animatedNode.equals(animation.mAnimatedValue)) {
if (animation.mEndCallback != null) {
// Invoke animation end callback with {finished: false}
WritableMap endCallbackResponse = Arguments.createMap();
endCallbackResponse.putBoolean("finished", false);
endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue);
animation.mEndCallback.invoke(endCallbackResponse);
} else if (mReactApplicationContext != null) {
// If no callback is passed in, this /may/ be an animation set up by the single-op
// instruction from JS, meaning that no jsi::functions are passed into native and
// we communicate via RCTDeviceEventEmitter instead of callbacks.
WritableMap params = Arguments.createMap();
params.putInt("animationId", animation.mId);
params.putBoolean("finished", false);
params.putDouble("value", animation.mAnimatedValue.mValue);
if (events == null) {
events = Arguments.createArray();
}
events.pushMap(params);
}
mActiveAnimations.removeAt(i);
i--;
}
}
if (events != null) {
mReactApplicationContext.emitDeviceEvent("onNativeAnimatedModuleAnimationFinished", events);
}
}
@UiThread
public void stopAnimation(int animationId) {
// in most of the cases there should never be more than a few active animations running at the
// same time. Therefore it does not make much sense to create an animationId -> animation
// object map that would require additional memory just to support the use-case of stopping
// an animation
WritableArray events = null;
for (int i = 0; i < mActiveAnimations.size(); i++) {
AnimationDriver animation = mActiveAnimations.valueAt(i);
if (animation.mId == animationId) {
if (animation.mEndCallback != null) {
// Invoke animation end callback with {finished: false}
WritableMap endCallbackResponse = Arguments.createMap();
endCallbackResponse.putBoolean("finished", false);
endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue);
animation.mEndCallback.invoke(endCallbackResponse);
} else if (mReactApplicationContext != null) {
// If no callback is passed in, this /may/ be an animation set up by the single-op
// instruction from JS, meaning that no jsi::functions are passed into native and
// we communicate via RCTDeviceEventEmitter instead of callbacks.
WritableMap params = Arguments.createMap();
params.putInt("animationId", animation.mId);
params.putBoolean("finished", false);
params.putDouble("value", animation.mAnimatedValue.mValue);
if (events == null) {
events = Arguments.createArray();
}
events.pushMap(params);
}
mActiveAnimations.removeAt(i);
break;
}
}
if (events != null) {
mReactApplicationContext.emitDeviceEvent("onNativeAnimatedModuleAnimationFinished", events);
}
// Do not throw an error in the case animation could not be found. We only keep "active"
// animations in the registry and there is a chance that Animated.js will enqueue a
// stopAnimation call after the animation has ended or the call will reach native thread only
// when the animation is already over.
}
@UiThread
public void connectAnimatedNodes(int parentNodeTag, int childNodeTag) {
AnimatedNode parentNode = mAnimatedNodes.get(parentNodeTag);
if (parentNode == null) {
throw new JSApplicationIllegalArgumentException(
"connectAnimatedNodes: Animated node with tag (parent) ["
+ parentNodeTag
+ "] does not exist");
}
AnimatedNode childNode = mAnimatedNodes.get(childNodeTag);
if (childNode == null) {
throw new JSApplicationIllegalArgumentException(
"connectAnimatedNodes: Animated node with tag (child) ["
+ childNodeTag
+ "] does not exist");
}
parentNode.addChild(childNode);
mUpdatedNodes.put(childNodeTag, childNode);
}
public void disconnectAnimatedNodes(int parentNodeTag, int childNodeTag) {
AnimatedNode parentNode = mAnimatedNodes.get(parentNodeTag);
if (parentNode == null) {
throw new JSApplicationIllegalArgumentException(
"disconnectAnimatedNodes: Animated node with tag (parent) ["
+ parentNodeTag
+ "] does not exist");
}
AnimatedNode childNode = mAnimatedNodes.get(childNodeTag);
if (childNode == null) {
throw new JSApplicationIllegalArgumentException(
"disconnectAnimatedNodes: Animated node with tag (child) ["
+ childNodeTag
+ "] does not exist");
}
parentNode.removeChild(childNode);
mUpdatedNodes.put(childNodeTag, childNode);
}
@UiThread
public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) {
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
if (node == null) {
throw new JSApplicationIllegalArgumentException(
"connectAnimatedNodeToView: Animated node with tag ["
+ animatedNodeTag
+ "] does not exist");
}
if (!(node instanceof PropsAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"connectAnimatedNodeToView: Animated node connected to view ["
+ viewTag
+ "] should be of type "
+ PropsAnimatedNode.class.getName());
}
if (mReactApplicationContext == null) {
throw new IllegalStateException(
"connectAnimatedNodeToView: Animated node could not be connected, no ReactApplicationContext: "
+ viewTag);
}
@Nullable
UIManager uiManager =
UIManagerHelper.getUIManagerForReactTag(mReactApplicationContext, viewTag);
if (uiManager == null) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new ReactNoCrashSoftException(
"connectAnimatedNodeToView: Animated node could not be connected to UIManager - uiManager disappeared for tag: "
+ viewTag));
return;
}
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
propsAnimatedNode.connectToView(viewTag, uiManager);
mUpdatedNodes.put(animatedNodeTag, node);
}
@UiThread
public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) {
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
if (node == null) {
throw new JSApplicationIllegalArgumentException(
"disconnectAnimatedNodeFromView: Animated node with tag ["
+ animatedNodeTag
+ "] does not exist");
}
if (!(node instanceof PropsAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"disconnectAnimatedNodeFromView: Animated node connected to view ["
+ viewTag
+ "] should be of type "
+ PropsAnimatedNode.class.getName());
}
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
propsAnimatedNode.disconnectFromView(viewTag);
}
@UiThread
public void getValue(int tag, Callback callback) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"getValue: Animated node with tag [" + tag + "] does not exist or is not a 'value' node");
}
double value = ((ValueAnimatedNode) node).getValue();
if (callback != null) {
callback.invoke(value);
return;
}
// If there's no callback, that means that JS is using the single-operation mode, and not
// passing any callbacks into Java.
// See NativeAnimatedHelper.js for details.
// Instead, we use RCTDeviceEventEmitter to pass data back to JS and emulate callbacks.
if (mReactApplicationContext == null) {
return;
}
WritableMap params = Arguments.createMap();
params.putInt("tag", tag);
params.putDouble("value", value);
mReactApplicationContext.emitDeviceEvent("onNativeAnimatedModuleGetValue", params);
}
@UiThread
public void restoreDefaultValues(int animatedNodeTag) {
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
// Restoring default values needs to happen before UIManager operations so it is
// possible the node hasn't been created yet if it is being connected and
// disconnected in the same batch. In that case we don't need to restore
// default values since it will never actually update the view.
if (node == null) {
return;
}
if (!(node instanceof PropsAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"Animated node connected to view [?] should be of type "
+ PropsAnimatedNode.class.getName());
}
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
propsAnimatedNode.restoreDefaultValues();
}
@UiThread
public void addAnimatedEventToView(
int viewTag, String eventHandlerName, ReadableMap eventMapping) {
int nodeTag = eventMapping.getInt("animatedValueTag");
AnimatedNode node = mAnimatedNodes.get(nodeTag);
if (node == null) {
throw new JSApplicationIllegalArgumentException(
"addAnimatedEventToView: Animated node with tag [" + nodeTag + "] does not exist");
}
if (!(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException(
"addAnimatedEventToView: Animated node on view ["
+ viewTag
+ "] connected to event handler ("
+ eventHandlerName
+ ") should be of type "
+ ValueAnimatedNode.class.getName());
}
ReadableArray path = eventMapping.getArray("nativeEventPath");
List<String> pathList = new ArrayList<>(path.size());
for (int i = 0; i < path.size(); i++) {
pathList.add(path.getString(i));
}
String eventName = normalizeEventName(eventHandlerName);
EventAnimationDriver eventDriver =
new EventAnimationDriver(eventName, viewTag, pathList, (ValueAnimatedNode) node);
mEventDrivers.add(eventDriver);
}
@UiThread
public void removeAnimatedEventFromView(
int viewTag, String eventHandlerName, int animatedValueTag) {
String eventName = normalizeEventName(eventHandlerName);
ListIterator<EventAnimationDriver> it = mEventDrivers.listIterator();
while (it.hasNext()) {
EventAnimationDriver driver = it.next();
if (eventName.equals(driver.mEventName)
&& viewTag == driver.mViewTag
&& animatedValueTag == driver.mValueNode.mTag) {
it.remove();
break;
}
}
}
@Override
public void onEventDispatch(final Event event) {
// Events can be dispatched from any thread so we have to make sure handleEvent is run from the
// UI thread.
if (UiThreadUtil.isOnUiThread()) {
handleEvent(event);
} else {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
handleEvent(event);
}
});
}
}
@UiThread
private void handleEvent(Event event) {
if (!mEventDrivers.isEmpty()) {
// If the event has a different name in native convert it to it's JS name.
// TODO T64216139 Remove dependency of UIManagerModule when the Constants are not in Native
// anymore
if (mReactApplicationContext == null) {
return;
}
UIManager uiManager =
UIManagerHelper.getUIManager(
mReactApplicationContext,
ViewUtil.getUIManagerType(event.getViewTag(), event.getSurfaceId()));
if (uiManager == null) {
return;
}
boolean foundAtLeastOneDriver = false;
Event.EventAnimationDriverMatchSpec matchSpec = event.getEventAnimationDriverMatchSpec();
for (EventAnimationDriver driver : mEventDrivers) {
if (matchSpec.match(driver.mViewTag, driver.mEventName)) {
foundAtLeastOneDriver = true;
stopAnimationsForNode(driver.mValueNode);
event.dispatchModern(driver);
mRunUpdateNodeList.add(driver.mValueNode);
}
}
if (foundAtLeastOneDriver) {
updateNodes(mRunUpdateNodeList);
mRunUpdateNodeList.clear();
}
}
}
/**
* Animation loop performs two BFSes over the graph of animated nodes. We use incremented {@code
* mAnimatedGraphBFSColor} to mark nodes as visited in each of the BFSes which saves additional
* loops for clearing "visited" states.
*
* <p>First BFS starts with nodes that are in {@code mUpdatedNodes} (that is, their value have
* been modified from JS in the last batch of JS operations) or directly attached to an active
* animation (hence linked to objects from {@code mActiveAnimations}). In that step we calculate
* an attribute {@code mActiveIncomingNodes}. The second BFS runs in topological order over the
* sub-graph of *active* nodes. This is done by adding node to the BFS queue only if all its
* "predecessors" have already been visited.
*/
@UiThread
public void runUpdates(long frameTimeNanos) {
UiThreadUtil.assertOnUiThread();
boolean hasFinishedAnimations = false;
for (int i = 0; i < mUpdatedNodes.size(); i++) {
AnimatedNode node = mUpdatedNodes.valueAt(i);
mRunUpdateNodeList.add(node);
}
// Clean mUpdatedNodes queue
mUpdatedNodes.clear();
for (int i = 0; i < mActiveAnimations.size(); i++) {
AnimationDriver animation = mActiveAnimations.valueAt(i);
animation.runAnimationStep(frameTimeNanos);
AnimatedNode valueNode = animation.mAnimatedValue;
mRunUpdateNodeList.add(valueNode);
if (animation.mHasFinished) {
hasFinishedAnimations = true;
}
}
updateNodes(mRunUpdateNodeList);
mRunUpdateNodeList.clear();
// Cleanup finished animations. Iterate over the array of animations and override ones that has
// finished, then resize `mActiveAnimations`.
if (hasFinishedAnimations) {
WritableArray events = null;
for (int i = mActiveAnimations.size() - 1; i >= 0; i--) {
AnimationDriver animation = mActiveAnimations.valueAt(i);
if (animation.mHasFinished) {
if (animation.mEndCallback != null) {
WritableMap endCallbackResponse = Arguments.createMap();
endCallbackResponse.putBoolean("finished", true);
endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue);
animation.mEndCallback.invoke(endCallbackResponse);
} else if (mReactApplicationContext != null) {
// If no callback is passed in, this /may/ be an animation set up by the single-op
// instruction from JS, meaning that no jsi::functions are passed into native and
// we communicate via RCTDeviceEventEmitter instead of callbacks.
WritableMap params = Arguments.createMap();
params.putInt("animationId", animation.mId);
params.putBoolean("finished", true);
params.putDouble("value", animation.mAnimatedValue.mValue);
if (events == null) {
events = Arguments.createArray();
}
events.pushMap(params);
}
mActiveAnimations.removeAt(i);
}
}
if (events != null) {
mReactApplicationContext.emitDeviceEvent("onNativeAnimatedModuleAnimationFinished", events);
}
}
}
@UiThread
private void updateNodes(List<AnimatedNode> nodes) {
int activeNodesCount = 0;
int updatedNodesCount = 0;
// STEP 1.
// BFS over graph of nodes. Update `mIncomingNodes` attribute for each node during that BFS.
// Store number of visited nodes in `activeNodesCount`. We "execute" active animations as a part
// of this step.
mAnimatedGraphBFSColor++; /* use new color */
if (mAnimatedGraphBFSColor == AnimatedNode.INITIAL_BFS_COLOR) {
// value "0" is used as an initial color for a new node, using it in BFS may cause some nodes
// to be skipped.
mAnimatedGraphBFSColor++;
}
Queue<AnimatedNode> nodesQueue = new ArrayDeque<>();
for (AnimatedNode node : nodes) {
if (node.mBFSColor != mAnimatedGraphBFSColor) {
node.mBFSColor = mAnimatedGraphBFSColor;
activeNodesCount++;
nodesQueue.add(node);
}
}
while (!nodesQueue.isEmpty()) {
AnimatedNode nextNode = nodesQueue.poll();
if (nextNode.mChildren != null) {
for (int i = 0; i < nextNode.mChildren.size(); i++) {
AnimatedNode child = nextNode.mChildren.get(i);
child.mActiveIncomingNodes++;
if (child.mBFSColor != mAnimatedGraphBFSColor) {
child.mBFSColor = mAnimatedGraphBFSColor;
activeNodesCount++;
nodesQueue.add(child);
}
}
}
}
// STEP 2
// BFS over the graph of active nodes in topological order -> visit node only when all its
// "predecessors" in the graph have already been visited. It is important to visit nodes in that
// order as they may often use values of their predecessors in order to calculate "next state"
// of their own. We start by determining the starting set of nodes by looking for nodes with
// `mActiveIncomingNodes = 0` (those can only be the ones that we start BFS in the previous
// step). We store number of visited nodes in this step in `updatedNodesCount`
mAnimatedGraphBFSColor++;
if (mAnimatedGraphBFSColor == AnimatedNode.INITIAL_BFS_COLOR) {
// see reasoning for this check a few lines above
mAnimatedGraphBFSColor++;
}
// find nodes with zero "incoming nodes", those can be either nodes from `mUpdatedNodes` or
// ones connected to active animations
for (AnimatedNode node : nodes) {
if (node.mActiveIncomingNodes == 0 && node.mBFSColor != mAnimatedGraphBFSColor) {
node.mBFSColor = mAnimatedGraphBFSColor;
updatedNodesCount++;
nodesQueue.add(node);
}
}
// Run main "update" loop
int cyclesDetected = 0;
while (!nodesQueue.isEmpty()) {
AnimatedNode nextNode = nodesQueue.poll();
try {
nextNode.update();
if (nextNode instanceof PropsAnimatedNode) {
// Send property updates to native view manager
((PropsAnimatedNode) nextNode).updateView();
}
} catch (JSApplicationCausedNativeException e) {
// An exception is thrown if the view hasn't been created yet. This can happen because
// views are created in batches. If this particular view didn't make it into a batch yet,
// the view won't exist and an exception will be thrown when attempting to start an
// animation on it.
//
// Eat the exception rather than crashing. The impact is that we may drop one or more
// frames of the animation.
FLog.e(TAG, "Native animation workaround, frame lost as result of race condition", e);
}
if (nextNode instanceof ValueAnimatedNode) {
// Potentially send events to JS when the node's value is updated
((ValueAnimatedNode) nextNode).onValueUpdate();
}
if (nextNode.mChildren != null) {
for (int i = 0; i < nextNode.mChildren.size(); i++) {
AnimatedNode child = nextNode.mChildren.get(i);
child.mActiveIncomingNodes--;
if (child.mBFSColor != mAnimatedGraphBFSColor && child.mActiveIncomingNodes == 0) {
child.mBFSColor = mAnimatedGraphBFSColor;
updatedNodesCount++;
nodesQueue.add(child);
} else if (child.mBFSColor == mAnimatedGraphBFSColor) {
cyclesDetected++;
}
}
}
}
// Verify that we've visited *all* active nodes. Throw otherwise as this could mean there is a
// cycle in animated node graph, or that the graph is only partially set up. We also take
// advantage of the fact that all active nodes are visited in the step above so that all the
// nodes properties `mActiveIncomingNodes` are set to zero.
// In Fabric there can be race conditions between the JS thread setting up or tearing down
// animated nodes, and Fabric executing them on the UI thread, leading to temporary inconsistent
// states.
if (activeNodesCount != updatedNodesCount) {
if (mWarnedAboutGraphTraversal) {
return;
}
mWarnedAboutGraphTraversal = true;
// Before crashing or logging soft exception, log details about current graph setup
FLog.e(TAG, "Detected animation cycle or disconnected graph. ");
for (AnimatedNode node : nodes) {
FLog.e(TAG, node.prettyPrintWithChildren());
}
// If we're running only in non-Fabric, we still throw an exception.
// In Fabric, it seems that animations enter an inconsistent state fairly often.
// We detect if the inconsistency is due to a cycle (a fatal error for which we must crash)
// or disconnected regions, indicating a partially-set-up animation graph, which is not
// fatal and can stay a warning.
String reason =
cyclesDetected > 0 ? "cycles (" + cyclesDetected + ")" : "disconnected regions";
IllegalStateException ex =
new IllegalStateException(
"Looks like animated nodes graph has "
+ reason
+ ", there are "
+ activeNodesCount
+ " but toposort visited only "
+ updatedNodesCount);
if (mEventListenerInitializedForFabric && cyclesDetected == 0) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, new ReactNoCrashSoftException(ex));
} else if (mEventListenerInitializedForFabric) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, new ReactNoCrashSoftException(ex));
} else {
throw ex;
}
} else {
mWarnedAboutGraphTraversal = false;
}
}
private String normalizeEventName(String eventHandlerName) {
// Fabric UIManager also makes this assumption
String eventName = eventHandlerName;
if (eventHandlerName.startsWith("on")) {
eventName = "top" + eventHandlerName.substring(2);
}
return eventName;
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
/**
* Native counterpart of object animated node (see AnimatedObject class in
* AnimatedImplementation.js)
*/
/* package */ @Nullsafe(Nullsafe.Mode.LOCAL)
class ObjectAnimatedNode extends AnimatedNode {
private static final String VALUE_KEY = "value";
private static final String NODE_TAG_KEY = "nodeTag";
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final JavaOnlyMap mConfig;
ObjectAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mConfig = JavaOnlyMap.deepClone(config);
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
}
public void collectViewUpdates(String propKey, JavaOnlyMap propsMap) {
ReadableType valueType = mConfig.getType(VALUE_KEY);
if (valueType == ReadableType.Map) {
propsMap.putMap(propKey, collectViewUpdatesHelper(mConfig.getMap(VALUE_KEY)));
} else if (valueType == ReadableType.Array) {
propsMap.putArray(propKey, collectViewUpdatesHelper(mConfig.getArray(VALUE_KEY)));
} else {
throw new IllegalArgumentException("Invalid value type for ObjectAnimatedNode");
}
}
private @Nullable JavaOnlyArray collectViewUpdatesHelper(@Nullable ReadableArray source) {
if (source == null) {
return null;
}
JavaOnlyArray result = new JavaOnlyArray();
for (int i = 0; i < source.size(); i++) {
switch (source.getType(i)) {
case Null:
result.pushNull();
break;
case Boolean:
result.pushBoolean(source.getBoolean(i));
break;
case Number:
result.pushDouble(source.getDouble(i));
break;
case String:
result.pushString(source.getString(i));
break;
case Map:
ReadableMap map = source.getMap(i);
if (map.hasKey(NODE_TAG_KEY) && map.getType(NODE_TAG_KEY) == ReadableType.Number) {
AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(map.getInt(NODE_TAG_KEY));
if (node == null) {
throw new IllegalArgumentException("Mapped value node does not exist");
} else if (node instanceof ValueAnimatedNode) {
ValueAnimatedNode valueAnimatedNode = (ValueAnimatedNode) node;
Object animatedObject = valueAnimatedNode.getAnimatedObject();
if (animatedObject instanceof Integer) {
result.pushInt((Integer) animatedObject);
} else if (animatedObject instanceof String) {
result.pushString((String) animatedObject);
} else {
result.pushDouble(valueAnimatedNode.getValue());
}
} else if (node instanceof ColorAnimatedNode) {
result.pushInt(((ColorAnimatedNode) node).getColor());
}
} else {
result.pushMap(collectViewUpdatesHelper(source.getMap(i)));
}
break;
case Array:
result.pushArray(collectViewUpdatesHelper(source.getArray(i)));
break;
}
}
return result;
}
private @Nullable JavaOnlyMap collectViewUpdatesHelper(@Nullable ReadableMap source) {
if (source == null) {
return null;
}
JavaOnlyMap result = new JavaOnlyMap();
ReadableMapKeySetIterator iter = source.keySetIterator();
while (iter.hasNextKey()) {
String propKey = iter.nextKey();
switch (source.getType(propKey)) {
case Null:
result.putNull(propKey);
break;
case Boolean:
result.putBoolean(propKey, source.getBoolean(propKey));
break;
case Number:
result.putDouble(propKey, source.getDouble(propKey));
break;
case String:
result.putString(propKey, source.getString(propKey));
break;
case Map:
ReadableMap map = source.getMap(propKey);
if (map != null
&& map.hasKey(NODE_TAG_KEY)
&& map.getType(NODE_TAG_KEY) == ReadableType.Number) {
AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(map.getInt(NODE_TAG_KEY));
if (node == null) {
throw new IllegalArgumentException("Mapped value node does not exist");
} else if (node instanceof ValueAnimatedNode) {
ValueAnimatedNode valueAnimatedNode = (ValueAnimatedNode) node;
Object animatedObject = valueAnimatedNode.getAnimatedObject();
if (animatedObject instanceof Integer) {
result.putInt(propKey, (Integer) animatedObject);
} else if (animatedObject instanceof String) {
result.putString(propKey, (String) animatedObject);
} else {
result.putDouble(propKey, valueAnimatedNode.getValue());
}
} else if (node instanceof ColorAnimatedNode) {
result.putInt(propKey, ((ColorAnimatedNode) node).getColor());
}
} else {
result.putMap(propKey, collectViewUpdatesHelper(map));
}
break;
case Array:
result.putArray(propKey, collectViewUpdatesHelper(source.getArray(propKey)));
break;
}
}
return result;
}
@Override
public String prettyPrint() {
return "ObjectAnimatedNode["
+ mTag
+ "]: mConfig: "
+ (mConfig != null ? mConfig.toString() : "null");
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.animated;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import java.util.HashMap;
import java.util.Map;
/**
* Animated node that represents view properties. There is a special handling logic implemented for
* the nodes of this type in {@link NativeAnimatedNodesManager} that is responsible for extracting a
* map of updated properties, which can be then passed down to the view.
*/
/*package*/ class PropsAnimatedNode extends AnimatedNode {
private int mConnectedViewTag = -1;
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final Map<String, Integer> mPropNodeMapping;
private final JavaOnlyMap mPropMap;
@Nullable private UIManager mUIManager;
PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
ReadableMap props = config.getMap("props");
ReadableMapKeySetIterator iter = props.keySetIterator();
mPropNodeMapping = new HashMap<>();
while (iter.hasNextKey()) {
String propKey = iter.nextKey();
int nodeIndex = props.getInt(propKey);
mPropNodeMapping.put(propKey, nodeIndex);
}
mPropMap = new JavaOnlyMap();
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
}
public void connectToView(int viewTag, UIManager uiManager) {
if (mConnectedViewTag != -1) {
throw new JSApplicationIllegalArgumentException(
"Animated node " + mTag + " is " + "already attached to a view: " + mConnectedViewTag);
}
mConnectedViewTag = viewTag;
mUIManager = uiManager;
}
public void disconnectFromView(int viewTag) {
if (mConnectedViewTag != viewTag && mConnectedViewTag != -1) {
throw new JSApplicationIllegalArgumentException(
"Attempting to disconnect view that has "
+ "not been connected with the given animated node: "
+ viewTag
+ " but is connected to view "
+ mConnectedViewTag);
}
mConnectedViewTag = -1;
}
public void restoreDefaultValues() {
// Cannot restore default values if this view has already been disconnected.
if (mConnectedViewTag == -1) {
return;
}
// Don't restore default values in Fabric.
// In Non-Fabric this had the effect of "restore the value to whatever the value was on the
// ShadowNode instead of in the View hierarchy". However, "synchronouslyUpdateViewOnUIThread"
// will not have that impact on Fabric, because the FabricUIManager doesn't have access to the
// ShadowNode layer.
if (ViewUtil.getUIManagerType(mConnectedViewTag) == UIManagerType.FABRIC) {
return;
}
ReadableMapKeySetIterator it = mPropMap.keySetIterator();
while (it.hasNextKey()) {
mPropMap.putNull(it.nextKey());
}
mUIManager.synchronouslyUpdateViewOnUIThread(mConnectedViewTag, mPropMap);
}
public final void updateView() {
if (mConnectedViewTag == -1) {
return;
}
for (Map.Entry<String, Integer> entry : mPropNodeMapping.entrySet()) {
@Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue());
if (node == null) {
throw new IllegalArgumentException("Mapped property node does not exist");
} else if (node instanceof StyleAnimatedNode) {
((StyleAnimatedNode) node).collectViewUpdates(mPropMap);
} else if (node instanceof ValueAnimatedNode) {
Object animatedObject = ((ValueAnimatedNode) node).getAnimatedObject();
if (animatedObject instanceof Integer) {
mPropMap.putInt(entry.getKey(), (Integer) animatedObject);
} else if (animatedObject instanceof String) {
mPropMap.putString(entry.getKey(), (String) animatedObject);
} else {
mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
}
} else if (node instanceof ColorAnimatedNode) {
mPropMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor());
} else if (node instanceof ObjectAnimatedNode) {
((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), mPropMap);
} else {
throw new IllegalArgumentException(
"Unsupported type of node used in property node " + node.getClass());
}
}
mUIManager.synchronouslyUpdateViewOnUIThread(mConnectedViewTag, mPropMap);
}
public View getConnectedView() {
try {
return mUIManager.resolveView(mConnectedViewTag);
} catch (IllegalViewOperationException ex) {
// resolveView throws an {@link IllegalViewOperationException} when the view doesn't exist
// (this can happen if the surface is being deallocated).
return null;
}
}
public String prettyPrint() {
return "PropsAnimatedNode["
+ mTag
+ "] connectedViewTag: "
+ mConnectedViewTag
+ " mPropNodeMapping: "
+ (mPropNodeMapping != null ? mPropNodeMapping.toString() : "null")
+ " mPropMap: "
+ (mPropMap != null ? mPropMap.toString() : "null");
}
}

View File

@@ -0,0 +1,207 @@
/*
* 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.animated;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReadableMap;
/**
* Implementation of {@link AnimationDriver} providing support for spring animations. The
* implementation has been copied from android implementation of Rebound library (see <a
* href="http://facebook.github.io/rebound/">http://facebook.github.io/rebound/</a>)
*/
/*package*/ @Nullsafe(Nullsafe.Mode.LOCAL)
class SpringAnimation extends AnimationDriver {
// maximum amount of time to simulate per physics iteration in seconds (4 frames at 60 FPS)
private static final double MAX_DELTA_TIME_SEC = 0.064;
// fixed timestep to use in the physics solver in seconds
private static final double SOLVER_TIMESTEP_SEC = 0.001;
// storage for the current and prior physics state while integration is occurring
private static class PhysicsState {
double position;
double velocity;
}
private long mLastTime;
private boolean mSpringStarted;
// configuration
private double mSpringStiffness;
private double mSpringDamping;
private double mSpringMass;
private double mInitialVelocity;
private boolean mOvershootClampingEnabled;
// all physics simulation objects are final and reused in each processing pass
private final PhysicsState mCurrentState = new PhysicsState();
private double mStartValue;
private double mEndValue;
// thresholds for determining when the spring is at rest
private double mRestSpeedThreshold;
private double mDisplacementFromRestThreshold;
private double mTimeAccumulator;
// for controlling loop
private int mIterations;
private int mCurrentLoop;
private double mOriginalValue;
SpringAnimation(ReadableMap config) {
mCurrentState.velocity = config.getDouble("initialVelocity");
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
mSpringStiffness = config.getDouble("stiffness");
mSpringDamping = config.getDouble("damping");
mSpringMass = config.getDouble("mass");
mInitialVelocity = mCurrentState.velocity;
mEndValue = config.getDouble("toValue");
mRestSpeedThreshold = config.getDouble("restSpeedThreshold");
mDisplacementFromRestThreshold = config.getDouble("restDisplacementThreshold");
mOvershootClampingEnabled = config.getBoolean("overshootClamping");
mIterations = config.hasKey("iterations") ? config.getInt("iterations") : 1;
mHasFinished = mIterations == 0;
mCurrentLoop = 0;
mTimeAccumulator = 0;
mSpringStarted = false;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
long frameTimeMillis = frameTimeNanos / 1000000;
if (!mSpringStarted) {
if (mCurrentLoop == 0) {
mOriginalValue = mAnimatedValue.mValue;
mCurrentLoop = 1;
}
mStartValue = mCurrentState.position = mAnimatedValue.mValue;
mLastTime = frameTimeMillis;
mTimeAccumulator = 0.0;
mSpringStarted = true;
}
advance((frameTimeMillis - mLastTime) / 1000.0);
mLastTime = frameTimeMillis;
mAnimatedValue.mValue = mCurrentState.position;
if (isAtRest()) {
if (mIterations == -1 || mCurrentLoop < mIterations) { // looping animation, return to start
mSpringStarted = false;
mAnimatedValue.mValue = mOriginalValue;
mCurrentLoop++;
} else { // animation has completed
mHasFinished = true;
}
}
}
/**
* get the displacement from rest for a given physics state
*
* @param state the state to measure from
* @return the distance displaced by
*/
private double getDisplacementDistanceForState(PhysicsState state) {
return Math.abs(mEndValue - state.position);
}
/**
* check if the current state is at rest
*
* @return is the spring at rest
*/
private boolean isAtRest() {
return Math.abs(mCurrentState.velocity) <= mRestSpeedThreshold
&& (getDisplacementDistanceForState(mCurrentState) <= mDisplacementFromRestThreshold
|| mSpringStiffness == 0);
}
/**
* Check if the spring is overshooting beyond its target.
*
* @return true if the spring is overshooting its target
*/
private boolean isOvershooting() {
return mSpringStiffness > 0
&& ((mStartValue < mEndValue && mCurrentState.position > mEndValue)
|| (mStartValue > mEndValue && mCurrentState.position < mEndValue));
}
private void advance(double realDeltaTime) {
if (isAtRest()) {
return;
}
// clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able
// to catch up in a subsequent advance if necessary.
double adjustedDeltaTime = realDeltaTime;
if (realDeltaTime > MAX_DELTA_TIME_SEC) {
adjustedDeltaTime = MAX_DELTA_TIME_SEC;
}
mTimeAccumulator += adjustedDeltaTime;
double c = mSpringDamping;
double m = mSpringMass;
double k = mSpringStiffness;
double v0 = -mInitialVelocity;
double zeta = c / (2 * Math.sqrt(k * m));
double omega0 = Math.sqrt(k / m);
double omega1 = omega0 * Math.sqrt(1.0 - (zeta * zeta));
double x0 = mEndValue - mStartValue;
double velocity;
double position;
double t = mTimeAccumulator;
if (zeta < 1) {
// Under damped
double envelope = Math.exp(-zeta * omega0 * t);
position =
mEndValue
- envelope
* ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t)
+ x0 * Math.cos(omega1 * t));
// This looks crazy -- it's actually just the derivative of the
// oscillation function
velocity =
zeta
* omega0
* envelope
* (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1
+ x0 * Math.cos(omega1 * t))
- envelope
* (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0)
- omega1 * x0 * Math.sin(omega1 * t));
} else {
// Critically damped spring
double envelope = Math.exp(-omega0 * t);
position = mEndValue - envelope * (x0 + (v0 + omega0 * x0) * t);
velocity = envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
}
mCurrentState.position = position;
mCurrentState.velocity = velocity;
// End the spring immediately if it is overshooting and overshoot clamping is enabled.
// Also make sure that if the spring was considered within a resting threshold that it's now
// snapped to its end value.
if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) {
// Don't call setCurrentValue because that forces a call to onSpringUpdate
if (mSpringStiffness > 0) {
mStartValue = mEndValue;
mCurrentState.position = mEndValue;
} else {
mEndValue = mCurrentState.position;
mStartValue = mEndValue;
}
mCurrentState.velocity = 0;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import java.util.HashMap;
import java.util.Map;
/**
* Native counterpart of style animated node (see AnimatedStyle class in AnimatedImplementation.js)
*/
/*package*/ class StyleAnimatedNode extends AnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final Map<String, Integer> mPropMapping;
StyleAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
ReadableMap style = config.getMap("style");
ReadableMapKeySetIterator iter = style.keySetIterator();
mPropMapping = new HashMap<>();
while (iter.hasNextKey()) {
String propKey = iter.nextKey();
int nodeIndex = style.getInt(propKey);
mPropMapping.put(propKey, nodeIndex);
}
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
}
public void collectViewUpdates(JavaOnlyMap propsMap) {
for (Map.Entry<String, Integer> entry : mPropMapping.entrySet()) {
@Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue());
if (node == null) {
throw new IllegalArgumentException("Mapped style node does not exist");
} else if (node instanceof TransformAnimatedNode) {
((TransformAnimatedNode) node).collectViewUpdates(propsMap);
} else if (node instanceof ValueAnimatedNode) {
Object animatedObject = ((ValueAnimatedNode) node).getAnimatedObject();
if (animatedObject instanceof Integer) {
propsMap.putInt(entry.getKey(), (Integer) animatedObject);
} else if (animatedObject instanceof String) {
propsMap.putString(entry.getKey(), (String) animatedObject);
} else {
propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
}
} else if (node instanceof ColorAnimatedNode) {
propsMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor());
} else if (node instanceof ObjectAnimatedNode) {
((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), propsMap);
} else {
throw new IllegalArgumentException(
"Unsupported type of node used in property node " + node.getClass());
}
}
}
public String prettyPrint() {
return "StyleAnimatedNode["
+ mTag
+ "] mPropMapping: "
+ (mPropMapping != null ? mPropMapping.toString() : "null");
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.animated;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Animated node that plays a role of value aggregator. It takes two or more value nodes as an input
* and outputs a difference of values outputted by those nodes.
*/
/*package*/ class SubtractionAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int[] mInputNodes;
public SubtractionAnimatedNode(
ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
ReadableArray inputNodes = config.getArray("input");
mInputNodes = new int[inputNodes.size()];
for (int i = 0; i < mInputNodes.length; i++) {
mInputNodes[i] = inputNodes.getInt(i);
}
}
@Override
public void update() {
for (int i = 0; i < mInputNodes.length; i++) {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
double value = ((ValueAnimatedNode) animatedNode).getValue();
if (i == 0) {
mValue = value;
} else {
mValue -= value;
}
} else {
throw new JSApplicationCausedNativeException(
"Illegal node ID set as an input for Animated.subtract node");
}
}
}
@Override
public String prettyPrint() {
return "SubtractionAnimatedNode["
+ mTag
+ "]: input nodes: "
+ (mInputNodes != null ? mInputNodes.toString() : "null")
+ " - super: "
+ super.prettyPrint();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.animated;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableMap;
/* package */ class TrackingAnimatedNode extends AnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int mAnimationId;
private final int mToValueNode;
private final int mValueNode;
private final JavaOnlyMap mAnimationConfig;
TrackingAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
mAnimationId = config.getInt("animationId");
mToValueNode = config.getInt("toValue");
mValueNode = config.getInt("value");
mAnimationConfig = JavaOnlyMap.deepClone(config.getMap("animationConfig"));
}
@Override
public void update() {
AnimatedNode toValue = mNativeAnimatedNodesManager.getNodeById(mToValueNode);
mAnimationConfig.putDouble("toValue", ((ValueAnimatedNode) toValue).getValue());
mNativeAnimatedNodesManager.startAnimatingNode(
mAnimationId, mValueNode, mAnimationConfig, null);
}
@Override
public String prettyPrint() {
return "TrackingAnimatedNode["
+ mTag
+ "]: animationID: "
+ mAnimationId
+ " toValueNode: "
+ mToValueNode
+ " valueNode: "
+ mValueNode
+ " animationConfig: "
+ mAnimationConfig;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.animated;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import java.util.ArrayList;
import java.util.List;
/**
* Native counterpart of transform animated node (see AnimatedTransform class in
* AnimatedImplementation.js)
*/
/* package */ class TransformAnimatedNode extends AnimatedNode {
private class TransformConfig {
public String mProperty;
}
private class AnimatedTransformConfig extends TransformConfig {
public int mNodeTag;
}
private class StaticTransformConfig extends TransformConfig {
public double mValue;
}
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final List<TransformConfig> mTransformConfigs;
TransformAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
ReadableArray transforms = config.getArray("transforms");
mTransformConfigs = new ArrayList<>(transforms.size());
for (int i = 0; i < transforms.size(); i++) {
ReadableMap transformConfigMap = transforms.getMap(i);
String property = transformConfigMap.getString("property");
String type = transformConfigMap.getString("type");
if (type.equals("animated")) {
AnimatedTransformConfig transformConfig = new AnimatedTransformConfig();
transformConfig.mProperty = property;
transformConfig.mNodeTag = transformConfigMap.getInt("nodeTag");
mTransformConfigs.add(transformConfig);
} else {
StaticTransformConfig transformConfig = new StaticTransformConfig();
transformConfig.mProperty = property;
transformConfig.mValue = transformConfigMap.getDouble("value");
mTransformConfigs.add(transformConfig);
}
}
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
}
public void collectViewUpdates(JavaOnlyMap propsMap) {
List<JavaOnlyMap> transforms = new ArrayList<>(mTransformConfigs.size());
for (TransformConfig transformConfig : mTransformConfigs) {
double value;
if (transformConfig instanceof AnimatedTransformConfig) {
int nodeTag = ((AnimatedTransformConfig) transformConfig).mNodeTag;
AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(nodeTag);
if (node == null) {
throw new IllegalArgumentException("Mapped style node does not exist");
} else if (node instanceof ValueAnimatedNode) {
value = ((ValueAnimatedNode) node).getValue();
} else {
throw new IllegalArgumentException(
"Unsupported type of node used as a transform child " + "node " + node.getClass());
}
} else {
value = ((StaticTransformConfig) transformConfig).mValue;
}
transforms.add(JavaOnlyMap.of(transformConfig.mProperty, value));
}
propsMap.putArray("transform", JavaOnlyArray.from(transforms));
}
@Override
public String prettyPrint() {
return "TransformAnimatedNode["
+ mTag
+ "]: mTransformConfigs: "
+ (mTransformConfigs != null ? mTransformConfigs.toString() : "null");
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.animated;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
/**
* Basic type of animated node that maps directly from {@code Animated.Value(x)} of Animated.js
* library.
*/
/*package*/ class ValueAnimatedNode extends AnimatedNode {
/*package*/ double mValue = Double.NaN;
/*package*/ double mOffset = 0;
private @Nullable AnimatedNodeValueListener mValueListener;
public ValueAnimatedNode() {
// empty constructor that can be used by subclasses
}
public ValueAnimatedNode(ReadableMap config) {
mValue = config.getDouble("value");
mOffset = config.getDouble("offset");
}
public double getValue() {
if (Double.isNaN(mOffset + mValue)) {
this.update();
}
return mOffset + mValue;
}
public Object getAnimatedObject() {
return null;
}
public void flattenOffset() {
mValue += mOffset;
mOffset = 0;
}
public void extractOffset() {
mOffset += mValue;
mValue = 0;
}
public void onValueUpdate() {
if (mValueListener == null) {
return;
}
mValueListener.onValueUpdate(getValue());
}
public void setValueListener(@Nullable AnimatedNodeValueListener listener) {
mValueListener = listener;
}
public String prettyPrint() {
return "ValueAnimatedNode[" + mTag + "]: value: " + mValue + " offset: " + mOffset;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.bridge;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.Nullable;
/**
* Listener for receiving activity events. Consider using {@link BaseActivityEventListener} if
* you're not interested in all the events sent to this interface.
*/
public interface ActivityEventListener {
/** Called when host (activity/service) receives an {@link Activity#onActivityResult} call. */
void onActivityResult(Activity activity, int requestCode, int resultCode, @Nullable Intent data);
/** Called when a new intent is passed to the activity */
void onNewIntent(Intent intent);
}

View File

@@ -0,0 +1,421 @@
/*
* 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.bridge;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import com.facebook.proguard.annotations.DoNotStrip;
import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@DoNotStrip
public class Arguments {
private static Object makeNativeObject(Object object) {
if (object == null) {
return null;
} else if (object instanceof Float
|| object instanceof Long
|| object instanceof Byte
|| object instanceof Short) {
return ((Number) object).doubleValue();
} else if (object.getClass().isArray()) {
return makeNativeArray(object);
} else if (object instanceof List) {
return makeNativeArray((List) object);
} else if (object instanceof Map) {
return makeNativeMap((Map<String, Object>) object);
} else if (object instanceof Bundle) {
return makeNativeMap((Bundle) object);
} else if (object instanceof JavaOnlyMap) {
return makeNativeMap(((JavaOnlyMap) object).toHashMap());
} else if (object instanceof JavaOnlyArray) {
return makeNativeArray(((JavaOnlyArray) object).toArrayList());
} else {
// Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
return object;
}
}
/**
* This method converts a List into a NativeArray. The data types supported are boolean, int,
* float, double, and String. List, Map, and Bundle objects, as well as arrays, containing values
* of the above types and/or null, or any recursive arrangement of these, are also supported. The
* best way to think of this is a way to generate a Java representation of a json list, from Java
* types which have a natural representation in json.
*/
public static WritableNativeArray makeNativeArray(List objects) {
WritableNativeArray nativeArray = new WritableNativeArray();
if (objects == null) {
return nativeArray;
}
for (Object elem : objects) {
elem = makeNativeObject(elem);
if (elem == null) {
nativeArray.pushNull();
} else if (elem instanceof Boolean) {
nativeArray.pushBoolean((Boolean) elem);
} else if (elem instanceof Integer) {
nativeArray.pushInt((Integer) elem);
} else if (elem instanceof Double) {
nativeArray.pushDouble((Double) elem);
} else if (elem instanceof String) {
nativeArray.pushString((String) elem);
} else if (elem instanceof WritableNativeArray) {
nativeArray.pushArray((WritableNativeArray) elem);
} else if (elem instanceof WritableNativeMap) {
nativeArray.pushMap((WritableNativeMap) elem);
} else {
throw new IllegalArgumentException("Could not convert " + elem.getClass());
}
}
return nativeArray;
}
/**
* This overload is like the above, but uses reflection to operate on any primitive or object
* type.
*/
public static <T> WritableNativeArray makeNativeArray(final Object objects) {
if (objects == null) {
return new WritableNativeArray();
}
// No explicit check for objects's type here. If it's not an array, the
// Array methods will throw IllegalArgumentException.
return makeNativeArray(
new AbstractList() {
public int size() {
return Array.getLength(objects);
}
public Object get(int index) {
return Array.get(objects, index);
}
});
}
private static void addEntry(WritableNativeMap nativeMap, String key, Object value) {
value = makeNativeObject(value);
if (value == null) {
nativeMap.putNull(key);
} else if (value instanceof Boolean) {
nativeMap.putBoolean(key, (Boolean) value);
} else if (value instanceof Integer) {
nativeMap.putInt(key, (Integer) value);
} else if (value instanceof Number) {
nativeMap.putDouble(key, ((Number) value).doubleValue());
} else if (value instanceof String) {
nativeMap.putString(key, (String) value);
} else if (value instanceof WritableNativeArray) {
nativeMap.putArray(key, (WritableNativeArray) value);
} else if (value instanceof WritableNativeMap) {
nativeMap.putMap(key, (WritableNativeMap) value);
} else {
throw new IllegalArgumentException("Could not convert " + value.getClass());
}
}
/**
* This method converts a Map into a NativeMap. Value types are supported as with makeNativeArray.
* The best way to think of this is a way to generate a Java representation of a json object, from
* Java types which have a natural representation in json.
*/
@DoNotStrip
public static WritableNativeMap makeNativeMap(Map<String, Object> objects) {
WritableNativeMap nativeMap = new WritableNativeMap();
if (objects == null) {
return nativeMap;
}
for (Map.Entry<String, Object> entry : objects.entrySet()) {
addEntry(nativeMap, entry.getKey(), entry.getValue());
}
return nativeMap;
}
/** Like the above, but takes a Bundle instead of a Map. */
@DoNotStrip
public static WritableNativeMap makeNativeMap(Bundle bundle) {
WritableNativeMap nativeMap = new WritableNativeMap();
if (bundle == null) {
return nativeMap;
}
for (String key : bundle.keySet()) {
addEntry(nativeMap, key, bundle.get(key));
}
return nativeMap;
}
/** This method should be used when you need to stub out creating NativeArrays in unit tests. */
public static WritableArray createArray() {
return new WritableNativeArray();
}
/** This method should be used when you need to stub out creating NativeMaps in unit tests. */
public static WritableMap createMap() {
return new WritableNativeMap();
}
public static WritableNativeArray fromJavaArgs(Object[] args) {
WritableNativeArray arguments = new WritableNativeArray();
for (int i = 0; i < args.length; i++) {
Object argument = args[i];
if (argument == null) {
arguments.pushNull();
continue;
}
Class argumentClass = argument.getClass();
if (argumentClass == Boolean.class) {
arguments.pushBoolean(((Boolean) argument).booleanValue());
} else if (argumentClass == Integer.class) {
arguments.pushDouble(((Integer) argument).doubleValue());
} else if (argumentClass == Double.class) {
arguments.pushDouble(((Double) argument).doubleValue());
} else if (argumentClass == Float.class) {
arguments.pushDouble(((Float) argument).doubleValue());
} else if (argumentClass == String.class) {
arguments.pushString(argument.toString());
} else if (argumentClass == WritableNativeMap.class) {
arguments.pushMap((WritableNativeMap) argument);
} else if (argumentClass == WritableNativeArray.class) {
arguments.pushArray((WritableNativeArray) argument);
} else {
throw new RuntimeException("Cannot convert argument of type " + argumentClass);
}
}
return arguments;
}
/**
* Convert an array to a {@link WritableArray}.
*
* @param array the array to convert. Supported types are: {@code String[]}, {@code Bundle[]},
* {@code int[]}, {@code float[]}, {@code double[]}, {@code boolean[]}.
* @return the converted {@link WritableArray}
* @throws IllegalArgumentException if the passed object is none of the above types
*/
public static WritableArray fromArray(Object array) {
WritableArray catalystArray = createArray();
if (array instanceof String[]) {
for (String v : (String[]) array) {
catalystArray.pushString(v);
}
} else if (array instanceof Bundle[]) {
for (Bundle v : (Bundle[]) array) {
catalystArray.pushMap(fromBundle(v));
}
} else if (array instanceof int[]) {
for (int v : (int[]) array) {
catalystArray.pushInt(v);
}
} else if (array instanceof float[]) {
for (float v : (float[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof double[]) {
for (double v : (double[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof boolean[]) {
for (boolean v : (boolean[]) array) {
catalystArray.pushBoolean(v);
}
} else if (array instanceof Parcelable[]) {
for (Parcelable v : (Parcelable[]) array) {
if (v instanceof Bundle) {
catalystArray.pushMap(fromBundle((Bundle) v));
} else {
throw new IllegalArgumentException("Unexpected array member type " + v.getClass());
}
}
} else {
throw new IllegalArgumentException("Unknown array type " + array.getClass());
}
return catalystArray;
}
/**
* Convert a {@link List} to a {@link WritableArray}.
*
* @param list the list to convert. Supported value types are: {@code null}, {@code String},
* {@code Bundle}, {@code List}, {@code Number}, {@code Boolean}, and all array types
* supported in {@link #fromArray(Object)}.
* @return the converted {@link WritableArray}
* @throws IllegalArgumentException if one of the values from the passed list is none of the above
* types
*/
public static WritableArray fromList(List list) {
WritableArray catalystArray = createArray();
for (Object obj : list) {
if (obj == null) {
catalystArray.pushNull();
} else if (obj.getClass().isArray()) {
catalystArray.pushArray(fromArray(obj));
} else if (obj instanceof Bundle) {
catalystArray.pushMap(fromBundle((Bundle) obj));
} else if (obj instanceof List) {
catalystArray.pushArray(fromList((List) obj));
} else if (obj instanceof String) {
catalystArray.pushString((String) obj);
} else if (obj instanceof Integer) {
catalystArray.pushInt((Integer) obj);
} else if (obj instanceof Number) {
catalystArray.pushDouble(((Number) obj).doubleValue());
} else if (obj instanceof Boolean) {
catalystArray.pushBoolean((Boolean) obj);
} else {
throw new IllegalArgumentException("Unknown value type " + obj.getClass());
}
}
return catalystArray;
}
/**
* Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle are:
*
* <p>
*
* <ul>
* <li>primitive types: int, float, double, boolean
* <li>arrays supported by {@link #fromArray(Object)}
* <li>lists supported by {@link #fromList(List)}
* <li>{@link Bundle} objects that are recursively converted to maps
* </ul>
*
* @param bundle the {@link Bundle} to convert
* @return the converted {@link WritableMap}
* @throws IllegalArgumentException if there are keys of unsupported types
*/
public static WritableMap fromBundle(Bundle bundle) {
WritableMap map = createMap();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
if (value == null) {
map.putNull(key);
} else if (value.getClass().isArray()) {
map.putArray(key, fromArray(value));
} else if (value instanceof String) {
map.putString(key, (String) value);
} else if (value instanceof Number) {
if (value instanceof Integer) {
map.putInt(key, (Integer) value);
} else {
map.putDouble(key, ((Number) value).doubleValue());
}
} else if (value instanceof Boolean) {
map.putBoolean(key, (Boolean) value);
} else if (value instanceof Bundle) {
map.putMap(key, fromBundle((Bundle) value));
} else if (value instanceof List) {
map.putArray(key, fromList((List) value));
} else {
throw new IllegalArgumentException("Could not convert " + value.getClass());
}
}
return map;
}
/**
* Convert a {@link WritableArray} to a {@link ArrayList}.
*
* @param readableArray the {@link WritableArray} to convert.
* @return the converted {@link ArrayList}.
*/
@Nullable
public static ArrayList toList(@Nullable ReadableArray readableArray) {
if (readableArray == null) {
return null;
}
ArrayList list = new ArrayList();
for (int i = 0; i < readableArray.size(); i++) {
switch (readableArray.getType(i)) {
case Null:
list.add(null);
break;
case Boolean:
list.add(readableArray.getBoolean(i));
break;
case Number:
double number = readableArray.getDouble(i);
if (number == Math.rint(number)) {
// Add as an integer
list.add((int) number);
} else {
// Add as a double
list.add(number);
}
break;
case String:
list.add(readableArray.getString(i));
break;
case Map:
list.add(toBundle(readableArray.getMap(i)));
break;
case Array:
list.add(toList(readableArray.getArray(i)));
break;
default:
throw new IllegalArgumentException("Could not convert object in array.");
}
}
return list;
}
/**
* Convert a {@link WritableMap} to a {@link Bundle}. Note: Each array is converted to an {@link
* ArrayList}.
*
* @param readableMap the {@link WritableMap} to convert.
* @return the converted {@link Bundle}.
*/
@Nullable
public static Bundle toBundle(@Nullable ReadableMap readableMap) {
if (readableMap == null) {
return null;
}
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
Bundle bundle = new Bundle();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ReadableType readableType = readableMap.getType(key);
switch (readableType) {
case Null:
bundle.putString(key, null);
break;
case Boolean:
bundle.putBoolean(key, readableMap.getBoolean(key));
break;
case Number:
// Can be int or double.
bundle.putDouble(key, readableMap.getDouble(key));
break;
case String:
bundle.putString(key, readableMap.getString(key));
break;
case Map:
bundle.putBundle(key, toBundle(readableMap.getMap(key)));
break;
case Array:
bundle.putSerializable(key, toList(readableMap.getArray(key)));
break;
default:
throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
}
}
return bundle;
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.bridge;
/**
* Like {@link AssertionError} but extends RuntimeException so that it may be caught by a {@link
* JSExceptionHandler}. See that class for more details. Used in conjunction with {@link
* SoftAssertions}.
*/
public class AssertionException extends RuntimeException {
public AssertionException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@DoNotStrip
public class BackgroundExecutor {
private static final String TAG = "FabricBackgroundExecutor";
private static class NamedThreadFactory implements ThreadFactory {
private final String mName;
public NamedThreadFactory(String name) {
mName = name;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setName(mName);
return thread;
}
}
private final ExecutorService mExecutorService;
@DoNotStrip
private BackgroundExecutor(String name) {
mExecutorService = Executors.newFixedThreadPool(1, new NamedThreadFactory(name));
}
@DoNotStrip
private void queueRunnable(Runnable runnable) {
// Very rarely, an NPE is hit here - probably has to do with deallocation
// race conditions and the JNI.
// It's not clear yet which of these is most prevalent, or if either is a concern.
// If we don't find these logs in production then we can probably safely remove the logging,
// but it's also cheap to leave it here.
if (runnable == null) {
ReactSoftExceptionLogger.logSoftException(
TAG, new ReactNoCrashSoftException("runnable is null"));
return;
}
final ExecutorService executorService = mExecutorService;
if (executorService == null) {
ReactSoftExceptionLogger.logSoftException(
TAG, new ReactNoCrashSoftException("executorService is null"));
return;
}
executorService.execute(runnable);
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.bridge;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.Nullable;
/** An empty implementation of {@link ActivityEventListener} */
public class BaseActivityEventListener implements ActivityEventListener {
/** @deprecated use {@link #onActivityResult(Activity, int, int, Intent)} instead. */
@Deprecated
public void onActivityResult(int requestCode, int resultCode, Intent data) {}
@Override
public void onActivityResult(
Activity activity, int requestCode, int resultCode, @Nullable Intent data) {}
@Override
public void onNewIntent(Intent intent) {}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.bridge;
import static com.facebook.infer.annotation.ThreadConfined.ANY;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
import com.facebook.react.common.annotations.StableReactNativeAPI;
import com.facebook.react.common.build.ReactBuildConfig;
import java.util.Map;
/**
* Base class for Catalyst native modules whose implementations are written in Java. Default
* implementations for {@link #initialize} and {@link #onCatalystInstanceDestroy} are provided for
* convenience. Subclasses which override these don't need to call {@code super} in case of
* overriding those methods as implementation of those methods is empty.
*
* <p>BaseJavaModules can be linked to Fragments' lifecycle events, {@link CatalystInstance}
* creation and destruction, by being called on the appropriate method when a life cycle event
* occurs.
*
* <p>Native methods can be exposed to JS with {@link ReactMethod} annotation. Those methods may
* only use limited number of types for their arguments:
*
* <ol>
* <li>primitives (boolean, int, float, double
* <li>{@link String} mapped from JS string
* <li>{@link ReadableArray} mapped from JS Array
* <li>{@link ReadableMap} mapped from JS Object
* <li>{@link Callback} mapped from js function and can be used only as a last parameter or in the
* case when it express success & error callback pair as two last arguments respectively.
* </ol>
*
* <p>All methods exposed as native to JS with {@link ReactMethod} annotation must return {@code
* void}.
*
* <p>Please note that it is not allowed to have multiple methods annotated with {@link ReactMethod}
* with the same name.
*/
@StableReactNativeAPI
public abstract class BaseJavaModule implements NativeModule {
// taken from Libraries/Utilities/MessageQueue.js
public static final String METHOD_TYPE_ASYNC = "async";
public static final String METHOD_TYPE_PROMISE = "promise";
public static final String METHOD_TYPE_SYNC = "sync";
private final @Nullable ReactApplicationContext mReactApplicationContext;
public BaseJavaModule() {
this(null);
}
public BaseJavaModule(@Nullable ReactApplicationContext reactContext) {
mReactApplicationContext = reactContext;
}
/** @return a map of constants this module exports to JS. Supports JSON types. */
@DeprecatedInNewArchitecture()
public @Nullable Map<String, Object> getConstants() {
return null;
}
@Override
public void initialize() {
// do nothing
}
@Override
public boolean canOverrideExistingModule() {
return false;
}
/**
* The CatalystInstance is going away with Venice. Therefore, the TurboModule infra introduces the
* invalidate() method to allow NativeModules to clean up after themselves.
*/
@Override
public void invalidate() {}
/**
* Subclasses can use this method to access {@link ReactApplicationContext} passed as a
* constructor.
*/
protected final ReactApplicationContext getReactApplicationContext() {
return Assertions.assertNotNull(
mReactApplicationContext,
"Tried to get ReactApplicationContext even though NativeModule wasn't instantiated with one");
}
/**
* Subclasses can use this method to access {@link ReactApplicationContext} passed as a
* constructor. Use this version to check that the underlying React Instance is active before
* returning, and automatically have SoftExceptions or debug information logged for you. Consider
* using this whenever calling ReactApplicationContext methods that require the React instance be
* alive.
*
* <p>This can return null at any time, but especially during teardown methods it's
* possible/likely.
*
* <p>Threading implications have not been analyzed fully yet, so assume this method is not
* thread-safe.
*/
@ThreadConfined(ANY)
protected @Nullable final ReactApplicationContext getReactApplicationContextIfActiveOrWarn() {
if (mReactApplicationContext.hasActiveReactInstance()) {
return mReactApplicationContext;
}
// We want to collect data about how often this happens, but SoftExceptions will cause a crash
// in debug mode, which isn't usually desirable.
String msg = "React Native Instance has already disappeared: requested by " + getName();
if (ReactBuildConfig.DEBUG) {
FLog.w(ReactConstants.TAG, msg);
} else {
ReactSoftExceptionLogger.logSoftException(ReactConstants.TAG, new RuntimeException(msg));
}
return null;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.bridge;
/**
* Interface that represent javascript callback function which can be passed to the native module as
* a method parameter.
*/
public interface Callback {
/**
* Schedule javascript function execution represented by this {@link Callback} instance
*
* @param args arguments passed to javascript callback method via bridge
*/
void invoke(Object... args);
}

View File

@@ -0,0 +1,34 @@
/*
* 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.bridge;
/** Implementation of javascript callback function that use Bridge to schedule method execution */
public final class CallbackImpl implements Callback {
private final JSInstance mJSInstance;
private final int mCallbackId;
private boolean mInvoked;
public CallbackImpl(JSInstance jsInstance, int callbackId) {
mJSInstance = jsInstance;
mCallbackId = callbackId;
mInvoked = false;
}
@Override
public void invoke(Object... args) {
if (mInvoked) {
throw new RuntimeException(
"Illegal callback invocation from native "
+ "module. This callback type only permits a single invocation from "
+ "native code.");
}
mJSInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
mInvoked = true;
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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.bridge;
import androidx.annotation.Nullable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry;
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;
import com.facebook.react.turbomodule.core.interfaces.NativeMethodCallInvokerHolder;
import java.util.Collection;
import java.util.List;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an environment allowing
* the invocation of JavaScript methods and lets a set of Java APIs be invocable from JavaScript as
* well.
*/
@DoNotStrip
public interface CatalystInstance
extends MemoryPressureListener, JSInstance, JSBundleLoaderDelegate {
void runJSBundle();
// Returns the status of running the JS bundle; waits for an answer if runJSBundle is running
boolean hasRunJSBundle();
/**
* Return the source URL of the JS Bundle that was run, or {@code null} if no JS bundle has been
* run yet.
*/
@Nullable
String getSourceURL();
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@Override
@DoNotStrip
void invokeCallback(int callbackID, NativeArrayInterface arguments);
@DoNotStrip
void callFunction(String module, String method, NativeArray arguments);
/**
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
* fully shut down other threads.
*/
void destroy();
boolean isDestroyed();
/** Initialize all the native modules */
@VisibleForTesting
void initialize();
ReactQueueConfiguration getReactQueueConfiguration();
<T extends JavaScriptModule> T getJSModule(Class<T> jsInterface);
<T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface);
@Nullable
<T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface);
@Nullable
NativeModule getNativeModule(String moduleName);
@Deprecated(
since =
"getJSIModule(JSIModuleType moduleType) is deprecated and will be deleted in the future. Please use ReactInstanceEventListener to subscribe for react instance events instead.")
JSIModule getJSIModule(JSIModuleType moduleType);
Collection<NativeModule> getNativeModules();
/**
* This method permits a CatalystInstance to extend the known Native modules. This provided
* registry contains only the new modules to load.
*/
void extendNativeModules(NativeModuleRegistry modules);
/**
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
* whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
* defined as there being some non-zero number of calls to JS that haven't resolved via a
* onBatchCompleted call. The listener should be purely passive and not affect application logic.
*/
void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
/**
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with {@link
* #addBridgeIdleDebugListener}
*/
void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
/** This method registers the file path of an additional JS segment by its ID. */
void registerSegment(int segmentId, String path);
@VisibleForTesting
void setGlobalVariable(String propName, String jsonValue);
/**
* Do not use this anymore. Use {@link #getRuntimeExecutor()} instead. Get the C pointer (as a
* long) to the JavaScriptCore context associated with this instance.
*
* <p>Use the following pattern to ensure that the JS context is not cleared while you are using
* it: JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder()
* synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); }
*/
@Deprecated
JavaScriptContextHolder getJavaScriptContextHolder();
RuntimeExecutor getRuntimeExecutor();
RuntimeScheduler getRuntimeScheduler();
@Deprecated
<T extends JSIModule> void addJSIModules(List<JSIModuleSpec<T>> jsiModules);
/**
* Returns a hybrid object that contains a pointer to a JS CallInvoker, which is used to schedule
* work on the JS Thread. Required for TurboModuleManager initialization.
*/
CallInvokerHolder getJSCallInvokerHolder();
/**
* Returns a hybrid object that contains a pointer to a NativeMethodCallInvoker, which is used to
* schedule work on the NativeModules thread. Required for TurboModuleManager initialization.
*/
NativeMethodCallInvokerHolder getNativeMethodCallInvokerHolder();
@Deprecated(
since =
"setTurboModuleManager(JSIModule getter) is deprecated and will be deleted in the future. Please use setTurboModuleRegistry(TurboModuleRegistry turboModuleRegistry)instead.")
void setTurboModuleManager(JSIModule getter);
@DeprecatedInNewArchitecture(
message =
"This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
void setTurboModuleRegistry(TurboModuleRegistry turboModuleRegistry);
@DeprecatedInNewArchitecture(
message =
"This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
void setFabricUIManager(UIManager fabricUIManager);
@DeprecatedInNewArchitecture(
message =
"This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
UIManager getFabricUIManager();
}

View File

@@ -0,0 +1,694 @@
/*
* 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.bridge;
import static com.facebook.infer.annotation.ThreadConfined.UI;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
import android.content.res.AssetManager;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.proguard.annotations.DoNotStripAny;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
import com.facebook.react.turbomodule.core.NativeMethodCallInvokerHolderImpl;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.TraceListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This provides an implementation of the public CatalystInstance instance. It is public because it
* is built by XReactInstanceManager which is in a different package.
*/
@DoNotStrip
public class CatalystInstanceImpl implements CatalystInstance {
static {
ReactBridge.staticInit();
}
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
public static class PendingJSCall {
public String mModule;
public String mMethod;
public @Nullable NativeArray mArguments;
public PendingJSCall(String module, String method, @Nullable NativeArray arguments) {
mModule = module;
mMethod = method;
mArguments = arguments;
}
void call(CatalystInstanceImpl catalystInstance) {
NativeArray arguments = mArguments != null ? mArguments : new WritableNativeArray();
catalystInstance.jniCallJSFunction(mModule, mMethod, arguments);
}
public String toString() {
return mModule
+ "."
+ mMethod
+ "("
+ (mArguments == null ? "" : mArguments.toString())
+ ")";
}
}
// Access from any thread
private final ReactQueueConfigurationImpl mReactQueueConfiguration;
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
private final String mJsPendingCallsTitleForTrace =
"pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
private volatile boolean mDestroyed = false;
private final TraceListener mTraceListener;
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
private final Object mJSCallsPendingInitLock = new Object();
private final NativeModuleRegistry mNativeModuleRegistry;
private final JSIModuleRegistry mJSIModuleRegistry = new JSIModuleRegistry();
private final JSExceptionHandler mJSExceptionHandler;
private final MessageQueueThread mNativeModulesQueueThread;
private boolean mInitialized = false;
private volatile boolean mAcceptCalls = false;
private boolean mJSBundleHasLoaded;
private @Nullable String mSourceURL;
private JavaScriptContextHolder mJavaScriptContextHolder;
private @Nullable TurboModuleRegistry mTurboModuleRegistry;
private @Nullable UIManager mFabricUIManager;
// C++ parts
private final HybridData mHybridData;
private static native HybridData initHybrid();
public native CallInvokerHolderImpl getJSCallInvokerHolder();
public native NativeMethodCallInvokerHolderImpl getNativeMethodCallInvokerHolder();
private CatalystInstanceImpl(
final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry nativeModuleRegistry,
final JSBundleLoader jsBundleLoader,
JSExceptionHandler jSExceptionHandler) {
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl");
mHybridData = initHybrid();
mReactQueueConfiguration =
ReactQueueConfigurationImpl.create(
reactQueueConfigurationSpec, new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mNativeModuleRegistry = nativeModuleRegistry;
mJSModuleRegistry = new JavaScriptModuleRegistry();
mJSBundleLoader = jsBundleLoader;
mJSExceptionHandler = jSExceptionHandler;
mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
mTraceListener = new JSProfilerTraceListener(this);
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge before initializeBridge");
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeCxxBridge");
initializeBridge(
new InstanceCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mNativeModulesQueueThread,
mNativeModuleRegistry.getJavaModules(this),
mNativeModuleRegistry.getCxxModules());
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
}
@DoNotStripAny
private static class InstanceCallback {
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
// In this case, the callback is held in C++ code, so the GC can't see it
// and determine there's an inaccessible cycle.
private final WeakReference<CatalystInstanceImpl> mOuter;
InstanceCallback(CatalystInstanceImpl outer) {
mOuter = new WeakReference<>(outer);
}
public void onBatchComplete() {
CatalystInstanceImpl impl = mOuter.get();
if (impl != null) {
impl.mNativeModulesQueueThread.runOnQueue(
() -> {
impl.mNativeModuleRegistry.onBatchComplete();
});
}
}
public void incrementPendingJSCalls() {
CatalystInstanceImpl impl = mOuter.get();
if (impl != null) {
impl.incrementPendingJSCalls();
}
}
public void decrementPendingJSCalls() {
CatalystInstanceImpl impl = mOuter.get();
if (impl != null) {
impl.decrementPendingJSCalls();
}
}
}
/**
* This method and the native below permits a CatalystInstance to extend the known Native modules.
* This registry contains only the new modules to load. The registry {@code mNativeModuleRegistry}
* updates internally to contain all the new modules, and generates the new registry for
* extracting just the new collections.
*/
@Override
public void extendNativeModules(NativeModuleRegistry modules) {
// Extend the Java-visible registry of modules
mNativeModuleRegistry.registerModules(modules);
Collection<JavaModuleWrapper> javaModules = modules.getJavaModules(this);
Collection<ModuleHolder> cxxModules = modules.getCxxModules();
// Extend the Cxx-visible registry of modules wrapped in appropriate interfaces
jniExtendNativeModules(javaModules, cxxModules);
}
private native void jniExtendNativeModules(
Collection<JavaModuleWrapper> javaModules, Collection<ModuleHolder> cxxModules);
private native void initializeBridge(
InstanceCallback callback,
JavaScriptExecutor jsExecutor,
MessageQueueThread jsQueue,
MessageQueueThread moduleQueue,
Collection<JavaModuleWrapper> javaModules,
Collection<ModuleHolder> cxxModules);
@Override
public void setSourceURLs(String deviceURL, String remoteURL) {
mSourceURL = deviceURL;
jniSetSourceURL(remoteURL);
}
@Override
public void registerSegment(int segmentId, String path) {
jniRegisterSegment(segmentId, path);
}
@Override
public void loadScriptFromAssets(
AssetManager assetManager, String assetURL, boolean loadSynchronously) {
mSourceURL = assetURL;
jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously);
}
@Override
public void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {
mSourceURL = sourceURL;
jniLoadScriptFromFile(fileName, sourceURL, loadSynchronously);
}
@Override
public void loadSplitBundleFromFile(String fileName, String sourceURL) {
jniLoadScriptFromFile(fileName, sourceURL, false);
}
private native void jniSetSourceURL(String sourceURL);
private native void jniRegisterSegment(int segmentId, String path);
private native void jniLoadScriptFromAssets(
AssetManager assetManager, String assetURL, boolean loadSynchronously);
private native void jniLoadScriptFromFile(
String fileName, String sourceURL, boolean loadSynchronously);
@Override
public void runJSBundle() {
FLog.d(ReactConstants.TAG, "CatalystInstanceImpl.runJSBundle()");
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
// incrementPendingJSCalls();
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
synchronized (mJSCallsPendingInitLock) {
// Loading the bundle is queued on the JS thread, but may not have
// run yet. It's safe to set this here, though, since any work it
// gates will be queued on the JS thread behind the load.
mAcceptCalls = true;
for (PendingJSCall function : mJSCallsPendingInit) {
function.call(this);
}
mJSCallsPendingInit.clear();
mJSBundleHasLoaded = true;
}
// This is registered after JS starts since it makes a JS call
Systrace.registerListener(mTraceListener);
}
@Override
public boolean hasRunJSBundle() {
synchronized (mJSCallsPendingInitLock) {
return mJSBundleHasLoaded && mAcceptCalls;
}
}
@Override
public @Nullable String getSourceURL() {
return mSourceURL;
}
private native void jniCallJSFunction(String module, String method, NativeArray arguments);
@Override
public void callFunction(final String module, final String method, final NativeArray arguments) {
callFunction(new PendingJSCall(module, method, arguments));
}
public void callFunction(PendingJSCall function) {
if (mDestroyed) {
final String call = function.toString();
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed: " + call);
return;
}
if (!mAcceptCalls) {
// Most of the time the instance is initialized and we don't need to acquire the lock
synchronized (mJSCallsPendingInitLock) {
if (!mAcceptCalls) {
mJSCallsPendingInit.add(function);
return;
}
}
}
function.call(this);
}
private native void jniCallJSCallback(int callbackID, NativeArray arguments);
@Override
public void invokeCallback(final int callbackID, final NativeArrayInterface arguments) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
return;
}
jniCallJSCallback(callbackID, (NativeArray) arguments);
}
/**
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
* fully shut down other threads.
*/
@Override
@ThreadConfined(UI)
public void destroy() {
FLog.d(ReactConstants.TAG, "CatalystInstanceImpl.destroy() start");
UiThreadUtil.assertOnUiThread();
if (mDestroyed) {
return;
}
// TODO: tell all APIs to shut down
ReactMarker.logMarker(ReactMarkerConstants.DESTROY_CATALYST_INSTANCE_START);
mDestroyed = true;
mNativeModulesQueueThread.runOnQueue(
() -> {
mNativeModuleRegistry.notifyJSInstanceDestroy();
mJSIModuleRegistry.notifyJSInstanceDestroy();
if (mFabricUIManager != null) {
mFabricUIManager.invalidate();
}
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
if (!wasIdle) {
listener.onTransitionToBridgeIdle();
}
listener.onBridgeDestroyed();
}
}
getReactQueueConfiguration()
.getJSQueueThread()
.runOnQueue(
() -> {
// We need to destroy the TurboModuleManager on the JS Thread
if (mTurboModuleRegistry != null) {
mTurboModuleRegistry.invalidate();
}
// Kill non-UI threads from neutral third party
// potentially expensive, so don't run on UI thread
new Thread(
() -> {
// contextHolder is used as a lock to guard against
// other users of the JS VM having the VM destroyed
// underneath them, so notify them before we reset
// Native
mJavaScriptContextHolder.clear();
mHybridData.resetNative();
getReactQueueConfiguration().destroy();
FLog.w(ReactConstants.TAG, "CatalystInstanceImpl.destroy() end");
ReactMarker.logMarker(
ReactMarkerConstants.DESTROY_CATALYST_INSTANCE_END);
},
"destroy_react_context")
.start();
});
});
// This is a noop if the listener was not yet registered.
Systrace.unregisterListener(mTraceListener);
}
@Override
public boolean isDestroyed() {
return mDestroyed;
}
/** Initialize all the native modules */
@VisibleForTesting
@Override
public void initialize() {
FLog.d(ReactConstants.TAG, "CatalystInstanceImpl.initialize()");
Assertions.assertCondition(
!mInitialized, "This catalyst instance has already been initialized");
// We assume that the instance manager blocks on running the JS bundle. If
// that changes, then we need to set mAcceptCalls just after posting the
// task that will run the js bundle.
Assertions.assertCondition(mAcceptCalls, "RunJSBundle hasn't completed.");
mInitialized = true;
mNativeModulesQueueThread.runOnQueue(
() -> {
mNativeModuleRegistry.notifyJSInstanceInitialized();
});
}
@Override
public ReactQueueConfiguration getReactQueueConfiguration() {
return mReactQueueConfiguration;
}
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
}
@Override
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
String moduleName = getNameFromAnnotation(nativeModuleInterface);
return getTurboModuleRegistry() != null && getTurboModuleRegistry().hasModule(moduleName)
? true
: mNativeModuleRegistry.hasModule(moduleName);
}
@Override
@Nullable
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return (T) getNativeModule(getNameFromAnnotation(nativeModuleInterface));
}
private TurboModuleRegistry getTurboModuleRegistry() {
if (ReactFeatureFlags.useTurboModules) {
return Assertions.assertNotNull(
mTurboModuleRegistry,
"TurboModules are enabled, but mTurboModuleRegistry hasn't been set.");
}
return null;
}
@Override
@Nullable
public NativeModule getNativeModule(String moduleName) {
if (getTurboModuleRegistry() != null) {
NativeModule module = getTurboModuleRegistry().getModule(moduleName);
if (module != null) {
return module;
}
}
return mNativeModuleRegistry.hasModule(moduleName)
? mNativeModuleRegistry.getModule(moduleName)
: null;
}
private <T extends NativeModule> String getNameFromAnnotation(Class<T> nativeModuleInterface) {
ReactModule annotation = nativeModuleInterface.getAnnotation(ReactModule.class);
if (annotation == null) {
throw new IllegalArgumentException(
"Could not find @ReactModule annotation in " + nativeModuleInterface.getCanonicalName());
}
return annotation.name();
}
// This is only used by com.facebook.react.modules.common.ModuleDataCleaner
@Override
public Collection<NativeModule> getNativeModules() {
Collection<NativeModule> nativeModules = new ArrayList<>();
nativeModules.addAll(mNativeModuleRegistry.getAllModules());
if (getTurboModuleRegistry() != null) {
for (NativeModule module : getTurboModuleRegistry().getModules()) {
nativeModules.add(module);
}
}
return nativeModules;
}
private native void jniHandleMemoryPressure(int level);
@Override
public void handleMemoryPressure(int level) {
if (mDestroyed) {
return;
}
jniHandleMemoryPressure(level);
}
/**
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
* whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
* defined as there being some non-zero number of calls to JS that haven't resolved via a
* onBatchComplete call. The listener should be purely passive and not affect application logic.
*/
@Override
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.add(listener);
}
/**
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with {@link
* #addBridgeIdleDebugListener}
*/
@Override
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.remove(listener);
}
@Override
public native void setGlobalVariable(String propName, String jsonValue);
@Override
public JavaScriptContextHolder getJavaScriptContextHolder() {
return mJavaScriptContextHolder;
}
public native RuntimeExecutor getRuntimeExecutor();
public native RuntimeScheduler getRuntimeScheduler();
@Override
@Deprecated
public <T extends JSIModule> void addJSIModules(List<JSIModuleSpec<T>> jsiModules) {
mJSIModuleRegistry.registerModules(jsiModules);
}
@Override
public JSIModule getJSIModule(JSIModuleType moduleType) {
return mJSIModuleRegistry.getModule(moduleType);
}
private native long getJavaScriptContext();
private void incrementPendingJSCalls() {
int oldPendingCalls = mPendingJSCalls.getAndIncrement();
boolean wasIdle = oldPendingCalls == 0;
Systrace.traceCounter(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, mJsPendingCallsTitleForTrace, oldPendingCalls + 1);
if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
mNativeModulesQueueThread.runOnQueue(
() -> {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeBusy();
}
});
}
}
public void setTurboModuleManager(JSIModule module) {
mTurboModuleRegistry = (TurboModuleRegistry) module;
}
@Override
public void setTurboModuleRegistry(TurboModuleRegistry turboModuleRegistry) {
mTurboModuleRegistry = turboModuleRegistry;
}
@Override
public void setFabricUIManager(UIManager fabricUIManager) {
mFabricUIManager = fabricUIManager;
}
@Override
public UIManager getFabricUIManager() {
return mFabricUIManager;
}
private void decrementPendingJSCalls() {
int newPendingCalls = mPendingJSCalls.decrementAndGet();
// TODO(9604406): handle case of web workers injecting messages to main thread
// Assertions.assertCondition(newPendingCalls >= 0);
boolean isNowIdle = newPendingCalls == 0;
Systrace.traceCounter(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, mJsPendingCallsTitleForTrace, newPendingCalls);
if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
mNativeModulesQueueThread.runOnQueue(
() -> {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeIdle();
}
});
}
}
private void onNativeException(Exception e) {
mJSExceptionHandler.handleException(e);
mReactQueueConfiguration
.getUIQueueThread()
.runOnQueue(
() -> {
destroy();
});
}
private class NativeExceptionHandler implements QueueThreadExceptionHandler {
@Override
public void handleException(Exception e) {
// Any Exception caught here is because of something in JS. Even if it's a bug in the
// framework/native code, it was triggered by JS and theoretically since we were able
// to set up the bridge, JS could change its logic, reload, and not trigger that crash.
onNativeException(e);
}
}
private static class JSProfilerTraceListener implements TraceListener {
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
// In this case, Systrace will keep the registered listener around forever
// if the CatalystInstanceImpl is not explicitly destroyed. These instances
// can still leak, but they are at least small.
private final WeakReference<CatalystInstanceImpl> mOuter;
public JSProfilerTraceListener(CatalystInstanceImpl outer) {
mOuter = new WeakReference<CatalystInstanceImpl>(outer);
}
@Override
public void onTraceStarted() {
CatalystInstanceImpl impl = mOuter.get();
if (impl != null) {
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
}
}
@Override
public void onTraceStopped() {
CatalystInstanceImpl impl = mOuter.get();
if (impl != null) {
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
}
}
}
public static class Builder {
private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable JSExceptionHandler mJSExceptionHandler;
public Builder setReactQueueConfigurationSpec(
ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
return this;
}
public Builder setRegistry(NativeModuleRegistry registry) {
mRegistry = registry;
return this;
}
public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
mJSBundleLoader = jsBundleLoader;
return this;
}
public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
mJSExecutor = jsExecutor;
return this;
}
public Builder setJSExceptionHandler(JSExceptionHandler handler) {
mJSExceptionHandler = handler;
return this;
}
public CatalystInstanceImpl build() {
return new CatalystInstanceImpl(
Assertions.assertNotNull(mReactQueueConfigurationSpec),
Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mJSExceptionHandler));
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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.bridge;
import android.content.Context;
import android.content.res.Resources;
import android.util.TypedValue;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants;
public class ColorPropConverter {
private static final String JSON_KEY = "resource_paths";
private static final String PREFIX_RESOURCE = "@";
private static final String PREFIX_ATTR = "?";
private static final String PACKAGE_DELIMITER = ":";
private static final String PATH_DELIMITER = "/";
private static final String ATTR = "attr";
private static final String ATTR_SEGMENT = "attr/";
public static Integer getColor(Object value, Context context) {
if (value == null) {
return null;
}
if (value instanceof Double) {
return ((Double) value).intValue();
}
if (context == null) {
throw new RuntimeException("Context may not be null.");
}
if (value instanceof ReadableMap) {
ReadableMap map = (ReadableMap) value;
ReadableArray resourcePaths = map.getArray(JSON_KEY);
if (resourcePaths == null) {
throw new JSApplicationCausedNativeException(
"ColorValue: The `" + JSON_KEY + "` must be an array of color resource path strings.");
}
for (int i = 0; i < resourcePaths.size(); i++) {
Integer result = resolveResourcePath(context, resourcePaths.getString(i));
if (result != null) {
return result;
}
}
throw new JSApplicationCausedNativeException(
"ColorValue: None of the paths in the `"
+ JSON_KEY
+ "` array resolved to a color resource.");
}
throw new JSApplicationCausedNativeException(
"ColorValue: the value must be a number or Object.");
}
public static Integer getColor(Object value, Context context, int defaultInt) {
try {
return getColor(value, context);
} catch (JSApplicationCausedNativeException e) {
FLog.w(ReactConstants.TAG, e, "Error converting ColorValue");
return defaultInt;
}
}
public static Integer resolveResourcePath(Context context, @Nullable String resourcePath) {
if (resourcePath == null || resourcePath.isEmpty()) {
return null;
}
boolean isResource = resourcePath.startsWith(PREFIX_RESOURCE);
boolean isThemeAttribute = resourcePath.startsWith(PREFIX_ATTR);
resourcePath = resourcePath.substring(1);
try {
if (isResource) {
return resolveResource(context, resourcePath);
} else if (isThemeAttribute) {
return resolveThemeAttribute(context, resourcePath);
}
} catch (Resources.NotFoundException exception) {
// The resource could not be found so do nothing to allow the for loop to continue and
// try the next fallback resource in the array. If none of the fallbacks are
// found then the exception immediately after the for loop will be thrown.
}
return null;
}
private static int resolveResource(Context context, String resourcePath) {
String[] pathTokens = resourcePath.split(PACKAGE_DELIMITER);
String packageName = context.getPackageName();
String resource = resourcePath;
if (pathTokens.length > 1) {
packageName = pathTokens[0];
resource = pathTokens[1];
}
String[] resourceTokens = resource.split(PATH_DELIMITER);
String resourceType = resourceTokens[0];
String resourceName = resourceTokens[1];
int resourceId = context.getResources().getIdentifier(resourceName, resourceType, packageName);
return ResourcesCompat.getColor(context.getResources(), resourceId, context.getTheme());
}
private static int resolveThemeAttribute(Context context, String resourcePath) {
String path = resourcePath.replaceAll(ATTR_SEGMENT, "");
String[] pathTokens = path.split(PACKAGE_DELIMITER);
String packageName = context.getPackageName();
String resourceName = path;
if (pathTokens.length > 1) {
packageName = pathTokens[0];
resourceName = pathTokens[1];
}
int resourceId = context.getResources().getIdentifier(resourceName, ATTR, packageName);
TypedValue outValue = new TypedValue();
Resources.Theme theme = context.getTheme();
if (theme.resolveAttribute(resourceId, outValue, true)) {
return outValue.data;
}
throw new Resources.NotFoundException();
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.bridge;
import static com.facebook.react.bridge.Arguments.*;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
/** Callback impl that calls directly into the cxx bridge. Created from C++. */
@DoNotStrip
public class CxxCallbackImpl implements Callback {
@DoNotStrip private final HybridData mHybridData;
@DoNotStrip
private CxxCallbackImpl(HybridData hybridData) {
mHybridData = hybridData;
}
@Override
public void invoke(Object... args) {
nativeInvoke(fromJavaArgs(args));
}
private native void nativeInvoke(NativeArray arguments);
}

View File

@@ -0,0 +1,19 @@
/*
* 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.bridge;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
/** This does nothing interesting, except avoid breaking existing code. */
@DoNotStrip
public class CxxModuleWrapper extends CxxModuleWrapperBase {
protected CxxModuleWrapper(HybridData hd) {
super(hd);
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.bridge;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* A Java Object which represents a cross-platform C++ module
*
* <p>This module implements the NativeModule interface but will never be invoked from Java, instead
* the underlying Cxx module will be extracted by the bridge and called directly.
*/
@DoNotStrip
public class CxxModuleWrapperBase implements NativeModule {
static {
ReactBridge.staticInit();
}
@DoNotStrip private HybridData mHybridData;
@Override
public native String getName();
@Override
public void initialize() {
// do nothing
}
@Override
public boolean canOverrideExistingModule() {
return false;
}
@Override
public void invalidate() {
mHybridData.resetNative();
}
// For creating a wrapper from C++, or from a derived class.
protected CxxModuleWrapperBase(HybridData hd) {
mHybridData = hd;
}
// Replace the current native module held by this wrapper by a new instance
protected void resetModule(HybridData hd) {
if (hd != mHybridData) {
mHybridData.resetNative();
mHybridData = hd;
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.bridge;
/** Crashy crashy exception handler. */
public class DefaultJSExceptionHandler implements JSExceptionHandler {
@Override
public void handleException(Exception e) {
if (e instanceof RuntimeException) {
// Because we are rethrowing the original exception, the original stacktrace will be
// preserved.
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.bridge;
import androidx.annotation.Nullable;
import com.facebook.yoga.YogaUnit;
import com.facebook.yoga.YogaValue;
public class DimensionPropConverter {
@Nullable
public static YogaValue getDimension(@Nullable Object value) {
if (value == null) {
return null;
}
if (value instanceof Double) {
return new YogaValue(((Double) value).floatValue(), YogaUnit.POINT);
}
if (value instanceof String) {
return YogaValue.parse((String) value);
}
throw new JSApplicationCausedNativeException(
"DimensionValue: the value must be a number or string.");
}
}

Some files were not shown because too many files have changed in this diff Show More