expo-modules-core 0.10.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +3 -3
  3. package/android/CMakeLists.txt +14 -5
  4. package/android/build.gradle +78 -28
  5. package/android/src/main/cpp/CachedReferencesRegistry.cpp +67 -0
  6. package/android/src/main/cpp/CachedReferencesRegistry.h +80 -0
  7. package/android/src/main/cpp/JNIFunctionBody.cpp +28 -12
  8. package/android/src/main/cpp/JNIFunctionBody.h +2 -2
  9. package/android/src/main/cpp/JNIInjector.cpp +4 -0
  10. package/android/src/main/cpp/JavaScriptModuleObject.cpp +86 -5
  11. package/android/src/main/cpp/JavaScriptModuleObject.h +27 -5
  12. package/android/src/main/cpp/JavaScriptRuntime.cpp +10 -12
  13. package/android/src/main/cpp/MethodMetadata.cpp +181 -40
  14. package/android/src/main/cpp/MethodMetadata.h +43 -3
  15. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +63 -10
  16. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +6 -0
  17. package/android/src/main/java/expo/modules/kotlin/activityaware/AppCompatActivityAware.kt +49 -0
  18. package/android/src/main/java/expo/modules/kotlin/activityaware/AppCompatActivityAwareHelper.kt +43 -0
  19. package/android/src/main/java/expo/modules/kotlin/activityaware/OnActivityAvailableListener.kt +18 -0
  20. package/android/src/main/java/expo/modules/kotlin/activityresult/ActivityResultsManager.kt +99 -0
  21. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultCaller.kt +25 -0
  22. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultContract.kt +27 -0
  23. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultFallbackCallback.kt +17 -0
  24. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt +30 -0
  25. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultRegistry.kt +358 -0
  26. package/android/src/main/java/expo/modules/kotlin/activityresult/DataPersistor.kt +135 -0
  27. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +34 -1
  28. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +7 -1
  29. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionBuilder.kt +0 -108
  30. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionComponent.kt +5 -2
  31. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromiseComponent.kt +5 -2
  32. package/android/src/main/java/expo/modules/kotlin/functions/SuspendFunctionComponent.kt +9 -2
  33. package/android/src/main/java/expo/modules/kotlin/functions/SyncFunctionComponent.kt +9 -1
  34. package/android/src/main/java/expo/modules/kotlin/jni/CppType.kt +1 -0
  35. package/android/src/main/java/expo/modules/kotlin/jni/JNIFunctionBody.kt +2 -2
  36. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +4 -2
  37. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +1 -1
  38. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +5 -454
  39. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +7 -15
  40. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +271 -0
  41. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionData.kt +21 -0
  42. package/android/src/main/java/expo/modules/kotlin/objects/PropertyComponent.kt +54 -0
  43. package/android/src/main/java/expo/modules/kotlin/objects/PropertyComponentBuilder.kt +32 -0
  44. package/android/src/main/java/expo/modules/kotlin/types/AnyTypeConverter.kt +36 -0
  45. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +7 -0
  46. package/android/src/main/java/expo/modules/kotlin/views/ViewGroupDefinitionBuilder.kt +0 -41
  47. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +0 -33
  48. package/build/PermissionsInterface.d.ts +29 -0
  49. package/build/PermissionsInterface.d.ts.map +1 -1
  50. package/build/PermissionsInterface.js +9 -0
  51. package/build/PermissionsInterface.js.map +1 -1
  52. package/ios/ExpoModulesCore.podspec +2 -1
  53. package/ios/JSI/EXJSIInstaller.mm +2 -0
  54. package/ios/JSI/EXJSIUtils.h +1 -0
  55. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +4 -3
  56. package/ios/Swift/AppContext.swift +2 -4
  57. package/ios/Swift/Classes/ClassComponentElementsBuilder.swift +2 -2
  58. package/ios/Swift/Exceptions/ChainableException.swift +3 -3
  59. package/ios/Swift/ExpoBridgeModule.swift +16 -2
  60. package/ios/Swift/Logging/Logger.swift +3 -0
  61. package/ios/Swift/Promise.swift +5 -1
  62. package/package.json +2 -2
  63. package/src/PermissionsInterface.ts +29 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,28 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.11.2 — 2022-07-16
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fix dangling pointer in the fbjni from the MethodMetadata::createPromiseBody on Android. ([#18206](https://github.com/expo/expo/pull/18206) by [@lukmccall](https://github.com/lukmccall))
18
+
19
+ ## 0.11.1 — 2022-07-11
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - Fixed a crash when remote debugging is enabled on Android. ([#18165](https://github.com/expo/expo/pull/18165) by [@kudo](https://github.com/kudo))
24
+
25
+ ## 0.11.0 — 2022-07-07
26
+
27
+ ### 🎉 New features
28
+
29
+ - Create `AppContext.registerForActivityResult` mechanism similar to [`ComponentActivity.registerForActivityResult`](https://developer.android.com/training/basics/intents/result)). ([#17572](https://github.com/expo/expo/pull/17572), ([#17987](https://github.com/expo/expo/pull/17987) by [@bbarthec](https://github.com/bbarthec))
30
+
31
+ ### 🐛 Bug fixes
32
+
33
+ - Added support for React Native 0.69.x ([#18006](https://github.com/expo/expo/pull/18006) by [@kudo](https://github.com/kudo))
34
+
13
35
  ## 0.10.0 — 2022-06-23
14
36
 
15
37
  ### 🎉 New features
@@ -28,6 +50,7 @@
28
50
  - Fix modules have not been deallocated during the application reload on iOS. ([#17285](https://github.com/expo/expo/pull/17285) by [@lukmccall](https://github.com/lukmccall))
29
51
  - Fix view props weren't recognized in the bare workflow on iOS. ([#17411](https://github.com/expo/expo/pull/17411) by [@lukmccall](https://github.com/lukmccall))
30
52
  - Fix support for optional function arguments on iOS. ([#17950](https://github.com/expo/expo/pull/17950) by [@barthap](https://github.com/barthap))
53
+ - Added support for React Native 0.69.x ([#17629](https://github.com/expo/expo/pull/17629) by [@kudo](https://github.com/kudo))
31
54
 
32
55
  ### 💡 Others
33
56
 
package/README.md CHANGED
@@ -36,7 +36,7 @@ Many React Native libraries come with platform-specific (native) code. This nati
36
36
  # Podfile
37
37
 
38
38
  require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
39
- require File.join(File.dirname(`node --print "require.resolve('react-native-unimodules/package.json')"`), "cocoapods.rb")
39
+ require File.join(File.dirname(`node --print "require.resolve('expo-modules-core/package.json')"`), "cocoapods.rb")
40
40
  require File.join(File.dirname(`node --print "require.resolve('expo-modules-core/package.json')"`), "scripts/autolinking")
41
41
 
42
42
  # ...
@@ -55,7 +55,7 @@ end
55
55
  ```groovy
56
56
  // app/build.gradle
57
57
 
58
- apply from: new File(["node", "--print", "require.resolve('react-native-unimodules/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy")
58
+ apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy")
59
59
  apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle")
60
60
  apply from: new File(["node", "--print", "require.resolve('expo-updates/package.json')"].execute(null, rootDir).text.trim(), "../scripts/create-manifest-android.gradle")
61
61
 
@@ -68,7 +68,7 @@ applyNativeModulesAppBuildGradle(project)
68
68
  ```groovy
69
69
  // settings.gradle
70
70
 
71
- apply from: new File(["node", "--print", "require.resolve('react-native-unimodules/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy");
71
+ apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy");
72
72
  includeUnimodulesProjects()
73
73
 
74
74
  apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
@@ -58,12 +58,21 @@ target_include_directories(
58
58
 
59
59
  find_library(LOG_LIB log)
60
60
 
61
- find_library(
62
- FOLLY_JSON_LIB
61
+ if(${REACT_NATIVE_TARGET_VERSION} LESS 69)
62
+ find_library(
63
+ FOLLY_LIB
63
64
  folly_json
64
65
  PATHS ${LIBRN_DIR}
65
66
  NO_CMAKE_FIND_ROOT_PATH
66
- )
67
+ )
68
+ else()
69
+ find_library(
70
+ FOLLY_LIB
71
+ folly_runtime
72
+ PATHS ${LIBRN_DIR}
73
+ NO_CMAKE_FIND_ROOT_PATH
74
+ )
75
+ endif()
67
76
 
68
77
  find_library(
69
78
  FBJNI_LIB
@@ -134,7 +143,7 @@ if (${FOR_HERMES})
134
143
  ${JSI_LIB}
135
144
  ${HERMES_LIB}
136
145
  ${REACT_NATIVE_JNI_LIB}
137
- ${FOLLY_JSON_LIB}
146
+ ${FOLLY_LIB}
138
147
  ${REACT_NATIVE_MODULES_CORE}
139
148
  android
140
149
  )
@@ -146,7 +155,7 @@ else ()
146
155
  ${JSI_LIB}
147
156
  ${JSEXECUTOR_LIB}
148
157
  ${REACT_NATIVE_JNI_LIB}
149
- ${FOLLY_JSON_LIB}
158
+ ${FOLLY_LIB}
150
159
  ${REACT_NATIVE_MODULES_CORE}
151
160
  android
152
161
  )
@@ -6,7 +6,7 @@ apply plugin: 'maven-publish'
6
6
  apply plugin: "de.undercouch.download"
7
7
 
8
8
  group = 'host.exp.exponent'
9
- version = '0.10.0'
9
+ version = '0.11.2'
10
10
 
11
11
  buildscript {
12
12
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -48,9 +48,11 @@ def isAndroidTest() {
48
48
  def downloadsDir = new File("$buildDir/downloads")
49
49
  def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")
50
50
 
51
- def REACT_NATIVE_DIR = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent
52
51
  def REACT_NATIVE_BUILD_FROM_SOURCE = findProject(":ReactAndroid") != null
53
- def REACT_NATIVE_SO_DIR = findProject(":ReactAndroid")
52
+ def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE
53
+ ? findProject(":ReactAndroid").getProjectDir().parent
54
+ : new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent
55
+ def REACT_NATIVE_SO_DIR = REACT_NATIVE_BUILD_FROM_SOURCE
54
56
  ? Paths.get(findProject(":ReactAndroid").getProjectDir().toString(), "build", "intermediates", "library_*", "*", "jni")
55
57
  : "${buildDir}/react-native-0*/jni"
56
58
 
@@ -60,6 +62,7 @@ file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { react
60
62
  def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION")
61
63
  def BOOST_VERSION = reactProperties.getProperty("BOOST_VERSION")
62
64
  def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION")
65
+ def REACT_NATIVE_TARGET_VERSION = reactProperties.getProperty("VERSION_NAME").split("\\.")[1].toInteger()
63
66
 
64
67
  def reactNativeThirdParty = new File("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/third-party")
65
68
 
@@ -69,7 +72,8 @@ def reactNativeArchitectures() {
69
72
  }
70
73
 
71
74
  def FOR_HERMES = false
72
- def HERMES_DIR = new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim()).parent
75
+ def HERMES_ENGINE_DIR = null
76
+ def HERMES_AAR = null
73
77
  if (findProject(":app")) {
74
78
  def appProjectExt = project(":app").ext
75
79
  if (appProjectExt.has("react")) {
@@ -81,6 +85,25 @@ if (findProject(":app")) {
81
85
  FOR_HERMES = (findProperty('expo.jsEngine') ?: "jsc") == "hermes"
82
86
  }
83
87
 
88
+ if (FOR_HERMES) {
89
+ // The `hermes-engine` package doesn't contain the correct version of the Hermes.
90
+ // The AAR we need to import is located in the React Native package.
91
+ // However, the version from RN doesn't include header files.
92
+ // To get those, we must fall back to the `hermes-engine` package.
93
+ HERMES_ENGINE_DIR = new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim()).parent
94
+ if (REACT_NATIVE_BUILD_FROM_SOURCE) {
95
+ // TODO(@lukmccall): Use Hermes from the `ReactAndroid:hermes-engine`
96
+ HERMES_AAR = file("$HERMES_ENGINE_DIR/android/hermes-debug.aar")
97
+ } else {
98
+ def prebuiltAAR = fileTree(REACT_NATIVE_DIR).matching { include "**/hermes-engine/**/hermes-engine-*-debug.aar" }
99
+ if (prebuiltAAR.any()) {
100
+ HERMES_AAR = prebuiltAAR.singleFile
101
+ } else {
102
+ HERMES_AAR = file("$HERMES_ENGINE_DIR/android/hermes-debug.aar")
103
+ }
104
+ }
105
+ }
106
+
84
107
  // Creating sources with comments
85
108
  task androidSourcesJar(type: Jar) {
86
109
  classifier = 'sources'
@@ -121,7 +144,7 @@ android {
121
144
  targetSdkVersion safeExtGet("targetSdkVersion", 31)
122
145
  consumerProguardFiles 'proguard-rules.pro'
123
146
  versionCode 1
124
- versionName "0.10.0"
147
+ versionName "0.11.2"
125
148
 
126
149
  testInstrumentationRunner "expo.modules.TestRunner"
127
150
 
@@ -130,9 +153,10 @@ android {
130
153
  abiFilters (*reactNativeArchitectures())
131
154
  arguments "-DREACT_NATIVE_DIR=${REACT_NATIVE_DIR}",
132
155
  "-DREACT_NATIVE_SO_DIR=${REACT_NATIVE_SO_DIR}",
156
+ "-DREACT_NATIVE_TARGET_VERSION=${REACT_NATIVE_TARGET_VERSION}",
133
157
  "-DBOOST_VERSION=${BOOST_VERSION}",
134
158
  "-DFOR_HERMES=${FOR_HERMES}",
135
- "-DHERMES_DIR=${HERMES_DIR}"
159
+ "-DHERMES_DIR=${HERMES_ENGINE_DIR}"
136
160
  }
137
161
  }
138
162
  }
@@ -158,6 +182,7 @@ android {
158
182
  "**/libjscexecutor.so",
159
183
  "**/libfbjni.so",
160
184
  "**/libfolly_json.so",
185
+ "**/libfolly_runtime.so",
161
186
  "**/libhermes.so",
162
187
  "**/libjsi.so",
163
188
  ]
@@ -223,19 +248,6 @@ dependencies {
223
248
  compileOnly 'com.facebook.fbjni:fbjni:0.2.2'
224
249
  extractHeaders 'com.facebook.fbjni:fbjni:0.2.2:headers'
225
250
  extractJNI 'com.facebook.fbjni:fbjni:0.2.2'
226
- def rnAARs = fileTree("${REACT_NATIVE_DIR}/android").matching { include "**/*.aar" }
227
- if (rnAARs.any()) {
228
- // node_modules/react-native has a .aar, extract headers
229
- if (rnAARs.size() > 1) {
230
- logger.error("More than one React Native AAR file has been found:")
231
- rnAARs.each { println(it) }
232
- throw new GradleException("Multiple React Native AARs found:\n${rnAARs.join("\n")}" +
233
- "\nRemove the old ones and try again")
234
- }
235
- def rnAAR = rnAARs.singleFile
236
- extractJNI(files(rnAAR))
237
- }
238
- // else - there is no prebuilt react-native.aar, this is most likely Expo Go
239
251
 
240
252
  testImplementation 'androidx.test:core:1.4.0'
241
253
  testImplementation 'junit:junit:4.13.1'
@@ -252,7 +264,7 @@ dependencies {
252
264
  androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
253
265
 
254
266
  if (FOR_HERMES) {
255
- androidTestImplementation files(new File(HERMES_DIR, "android/hermes-debug.aar"))
267
+ androidTestImplementation files(HERMES_AAR)
256
268
  } else {
257
269
  androidTestImplementation "org.webkit:android-jsc:+"
258
270
  }
@@ -276,6 +288,44 @@ task createNativeDepsDirectories() {
276
288
  // END UTILS
277
289
 
278
290
  // JNI
291
+ def extractReactNativeAAR = { buildType ->
292
+ def suffix = buildType == 'Debug' ? '-debug' : '-release'
293
+ def rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*${suffix}.aar" }
294
+ if (rnAARs.isEmpty()) {
295
+ rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*.aar" }
296
+ }
297
+ if (rnAARs.any()) {
298
+ // node_modules/react-native has a .aar, extract headers
299
+ if (rnAARs.size() > 1) {
300
+ logger.error("More than one React Native AAR file has been found:")
301
+ rnAARs.each {println(it) }
302
+ throw new GradleException("Multiple React Native AARs found:\n${rnAARs.join("\n")}" +
303
+ "\nRemove the old ones and try again")
304
+ }
305
+ }
306
+ def rnAAR = rnAARs.singleFile
307
+ def file = rnAAR.absoluteFile
308
+ def packageName = file.name.tokenize('-')[0]
309
+ copy {
310
+ from zipTree(file)
311
+ into "$buildDir/$file.name"
312
+ include "jni/**/*"
313
+ }
314
+ }
315
+
316
+ task extractReactNativeAARRelease {
317
+ doLast {
318
+ extractReactNativeAAR('Release')
319
+ }
320
+ }
321
+
322
+ task extractReactNativeAARDebug {
323
+ doLast {
324
+ extractReactNativeAAR('Debug')
325
+ }
326
+ }
327
+
328
+
279
329
  task extractAARHeaders {
280
330
  doLast {
281
331
  configurations.extractHeaders.files.each {
@@ -305,7 +355,10 @@ task extractJNIFiles {
305
355
 
306
356
  // BOOST
307
357
  task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) {
308
- src("https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz")
358
+ def srcUrl = REACT_NATIVE_TARGET_VERSION >= 69
359
+ ? "https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz"
360
+ : "https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz"
361
+ src(srcUrl)
309
362
  onlyIfNewer(true)
310
363
  overwrite(false)
311
364
  dest(new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz"))
@@ -374,12 +427,7 @@ task prepareHermes() {
374
427
  return
375
428
  }
376
429
 
377
- def hermesAAR = file("$HERMES_DIR/android/hermes-debug.aar")
378
- if (!hermesAAR.exists()) {
379
- throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"", null)
380
- }
381
-
382
- def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
430
+ def soFiles = zipTree(HERMES_AAR).matching({ it.include "**/*.so" })
383
431
 
384
432
  copy {
385
433
  from soFiles
@@ -397,11 +445,13 @@ afterEvaluate {
397
445
 
398
446
  tasks.whenTaskAdded { task ->
399
447
  if (!task.name.contains("Clean") && (task.name.contains('externalNativeBuild') || task.name.startsWith('configureCMake'))) {
448
+ def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
400
449
  task.dependsOn(extractAARHeaders)
401
450
  task.dependsOn(extractJNIFiles)
402
451
  if (REACT_NATIVE_BUILD_FROM_SOURCE) {
403
- def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
404
452
  task.dependsOn(":ReactAndroid:copy${buildType}JniLibsProjectOnly")
453
+ } else {
454
+ task.dependsOn("extractReactNativeAAR${buildType}")
405
455
  }
406
456
  } else if (task.name.startsWith('generateJsonModel') && REACT_NATIVE_BUILD_FROM_SOURCE) {
407
457
  def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
@@ -0,0 +1,67 @@
1
+ // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
+
3
+ #include "CachedReferencesRegistry.h"
4
+
5
+ #include <utility>
6
+
7
+ namespace expo {
8
+ std::shared_ptr<CachedReferencesRegistry> CachedReferencesRegistry::instance() {
9
+ static std::shared_ptr<CachedReferencesRegistry> singleton{new CachedReferencesRegistry};
10
+ return singleton;
11
+ }
12
+
13
+ void CachedReferencesRegistry::loadJClasses(JNIEnv *env) {
14
+ loadJClass(env, "java/lang/Double", {
15
+ {"<init>", "(D)V"}
16
+ });
17
+
18
+ loadJClass(env, "java/lang/Boolean", {
19
+ {"<init>", "(Z)V"}
20
+ });
21
+
22
+ loadJClass(env, "com/facebook/react/bridge/PromiseImpl", {
23
+ {"<init>", "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"}
24
+ });
25
+
26
+ loadJClass(env, "java/lang/Object", {});
27
+ }
28
+
29
+ void CachedReferencesRegistry::loadJClass(
30
+ JNIEnv *env,
31
+ const std::string &name,
32
+ const std::vector<std::pair<std::string, std::string>> &methodsNames
33
+ ) {
34
+ // Note this clazz variable points to a leaked global reference.
35
+ // This is appropriate for classes that are never unloaded which is any class in an Android app.
36
+ auto clazz = (jclass) env->NewGlobalRef(env->FindClass(name.c_str()));
37
+
38
+ MethodHashMap methods;
39
+ methods.reserve(methodsNames.size());
40
+
41
+ for (auto &method: methodsNames) {
42
+ methods.insert(
43
+ {method, env->GetMethodID(clazz, method.first.c_str(), method.second.c_str())}
44
+ );
45
+ }
46
+
47
+ jClassRegistry.insert(
48
+ {name, CachedJClass(clazz, std::move(methods))}
49
+ );
50
+ }
51
+
52
+ CachedReferencesRegistry::CachedJClass &CachedReferencesRegistry::getJClass(
53
+ const std::string &className
54
+ ) {
55
+ return jClassRegistry.at(className);
56
+ }
57
+
58
+ jmethodID CachedReferencesRegistry::CachedJClass::getMethod(const std::string &name,
59
+ const std::string &signature) {
60
+ return methods.at({name, signature});
61
+ }
62
+
63
+ CachedReferencesRegistry::CachedJClass::CachedJClass(
64
+ jclass clazz,
65
+ MethodHashMap methods
66
+ ) : clazz(clazz), methods(std::move(methods)) {}
67
+ } // namespace expo
@@ -0,0 +1,80 @@
1
+ // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
+
3
+ #pragma once
4
+
5
+ #include <jsi/jsi.h>
6
+ #include <fbjni/fbjni.h>
7
+ #include "boost/functional/hash.hpp"
8
+
9
+ #include <memory>
10
+ #include <unordered_map>
11
+
12
+ namespace jni = facebook::jni;
13
+ namespace jsi = facebook::jsi;
14
+
15
+ namespace expo {
16
+ using MethodHashMap = std::unordered_map<
17
+ std::pair<std::string, std::string>,
18
+ jmethodID,
19
+ boost::hash<std::pair<std::string, std::string>>
20
+ >;
21
+
22
+ /**
23
+ * Singleton registry used to store references to often used Java classes.
24
+ *
25
+ * TODO(@lukmccall): also store reference to jsi objects like `Promise`.
26
+ */
27
+ class CachedReferencesRegistry {
28
+ public:
29
+ /**
30
+ * An entry in the Java class registry.
31
+ */
32
+ class CachedJClass {
33
+ public:
34
+ CachedJClass(jclass clazz, MethodHashMap methods);
35
+
36
+ /**
37
+ * A bare reference to the class object.
38
+ */
39
+ jclass clazz;
40
+
41
+ /**
42
+ * Returns a cached method id for provided method name and signature.
43
+ */
44
+ jmethodID getMethod(const std::string &name, const std::string &signature);
45
+
46
+ private:
47
+ MethodHashMap methods;
48
+ };
49
+
50
+ CachedReferencesRegistry(CachedReferencesRegistry const &) = delete;
51
+
52
+ CachedReferencesRegistry &operator=(CachedReferencesRegistry const &) = delete;
53
+
54
+ /**
55
+ * Gets a singleton instance
56
+ */
57
+ static std::shared_ptr<CachedReferencesRegistry> instance();
58
+
59
+ /**
60
+ * Gets a cached Java class entry.
61
+ */
62
+ CachedJClass &getJClass(const std::string &className);
63
+
64
+ /**
65
+ * Loads predefined set of Java classes and stores them
66
+ */
67
+ void loadJClasses(JNIEnv *env);
68
+
69
+ private:
70
+ CachedReferencesRegistry() = default;
71
+
72
+ std::unordered_map<std::string, CachedJClass> jClassRegistry;
73
+
74
+ void loadJClass(
75
+ JNIEnv *env,
76
+ const std::string &name,
77
+ const std::vector<std::pair<std::string, std::string>> &methods
78
+ );
79
+ };
80
+ } // namespace expo
@@ -1,28 +1,44 @@
1
1
  // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
2
 
3
3
  #include "JNIFunctionBody.h"
4
+ #include "CachedReferencesRegistry.h"
4
5
 
5
6
  namespace jni = facebook::jni;
6
7
  namespace react = facebook::react;
7
8
 
8
9
  namespace expo {
9
10
  jni::local_ref<react::ReadableNativeArray::javaobject>
10
- JNIFunctionBody::invoke(react::ReadableNativeArray::javaobject &&args) {
11
- static const auto method = getClass()->getMethod<
12
- react::ReadableNativeArray::javaobject(react::ReadableNativeArray::javaobject)
13
- >(
14
- "invoke"
15
- );
11
+ JNIFunctionBody::invoke(jobjectArray args) {
12
+ // Do NOT use getClass here!
13
+ // Method obtained from `getClass` will point to the overridden version of the method.
14
+ // Because of that, it can't be cached - we will try to invoke the nonexistent method
15
+ // if we receive an object of a different class than the one used to obtain the method id.
16
+ // The only cacheable method id can be obtain from the base class.
17
+ static const auto method = jni::findClassLocal("expo/modules/kotlin/jni/JNIFunctionBody")
18
+ ->getMethod<jni::local_ref<react::ReadableNativeArray::javaobject>(jobjectArray)>(
19
+ "invoke",
20
+ "([Ljava/lang/Object;)Lcom/facebook/react/bridge/ReadableNativeArray;"
21
+ );
16
22
 
17
23
  return method(this->self(), args);
18
24
  }
19
25
 
20
- void JNIAsyncFunctionBody::invoke(react::ReadableNativeArray::javaobject &&args, jobject promise) {
21
- static const auto method = getClass()->getMethod<
22
- void(react::ReadableNativeArray::javaobject, jobject)
23
- >(
24
- "invoke"
25
- );
26
+ void JNIAsyncFunctionBody::invoke(
27
+ jobjectArray args,
28
+ jobject promise
29
+ ) {
30
+ // Do NOT use getClass here!
31
+ // Method obtained from `getClass` will point to the overridden version of the method.
32
+ // Because of that, it can't be cached - we will try to invoke the nonexistent method
33
+ // if we receive an object of a different class than the one used to obtain the method id.
34
+ // The only cacheable method id can be obtain from the base class.
35
+ static const auto method = jni::findClassLocal("expo/modules/kotlin/jni/JNIAsyncFunctionBody")
36
+ ->getMethod<
37
+ void(jobjectArray , jobject)
38
+ >(
39
+ "invoke",
40
+ "([Ljava/lang/Object;Ljava/lang/Object;)V"
41
+ );
26
42
 
27
43
  method(this->self(), args, promise);
28
44
  }
@@ -24,7 +24,7 @@ public:
24
24
  * @return result of the Kotlin function
25
25
  */
26
26
  jni::local_ref<react::ReadableNativeArray::javaobject> invoke(
27
- react::ReadableNativeArray::javaobject &&args
27
+ jobjectArray args
28
28
  );
29
29
  };
30
30
 
@@ -43,7 +43,7 @@ public:
43
43
  * @param promise that will be resolve or rejected in the Kotlin's implementation
44
44
  */
45
45
  void invoke(
46
- react::ReadableNativeArray::javaobject &&args,
46
+ jobjectArray args,
47
47
  jobject promise
48
48
  );
49
49
  };
@@ -4,6 +4,7 @@
4
4
  #include "JavaScriptModuleObject.h"
5
5
  #include "JavaScriptValue.h"
6
6
  #include "JavaScriptObject.h"
7
+ #include "CachedReferencesRegistry.h"
7
8
 
8
9
  #include <jni.h>
9
10
  #include <fbjni/fbjni.h>
@@ -11,6 +12,9 @@
11
12
  // Install all jni bindings
12
13
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
13
14
  return facebook::jni::initialize(vm, [] {
15
+ // Loads references to often use Java classes
16
+ expo::CachedReferencesRegistry::instance()->loadJClasses(jni::Environment::current());
17
+
14
18
  expo::JSIInteropModuleRegistry::registerNatives();
15
19
  expo::JavaScriptModuleObject::registerNatives();
16
20
  expo::JavaScriptValue::registerNatives();