expo-modules-core 1.10.0 → 1.11.0

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 (90) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/ExpoModulesCore.podspec +4 -3
  3. package/android/CMakeLists.txt +1 -1
  4. package/android/build.gradle +2 -2
  5. package/android/proguard-rules.pro +6 -2
  6. package/android/src/fabric/CMakeLists.txt +2 -1
  7. package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +5 -0
  8. package/android/src/main/cpp/JSIInteropModuleRegistry.h +4 -0
  9. package/android/src/main/cpp/MethodMetadata.cpp +13 -0
  10. package/android/src/main/cpp/types/CppType.h +9 -8
  11. package/android/src/main/cpp/types/ExpectedType.cpp +3 -0
  12. package/android/src/main/cpp/types/FrontendConverter.cpp +27 -0
  13. package/android/src/main/cpp/types/FrontendConverter.h +15 -0
  14. package/android/src/main/cpp/types/FrontendConverterProvider.cpp +1 -0
  15. package/android/src/main/cpp/types/JNIToJSIConverter.cpp +93 -6
  16. package/android/src/main/cpp/types/JNIToJSIConverter.h +6 -0
  17. package/android/src/main/java/expo/modules/adapters/react/permissions/PermissionsService.kt +11 -1
  18. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +3 -0
  19. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +1 -1
  20. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +9 -8
  21. package/android/src/main/java/expo/modules/kotlin/events/KModuleEventEmitterWrapper.kt +1 -1
  22. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +1 -1
  23. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +19 -10
  24. package/android/src/main/java/expo/modules/kotlin/functions/BaseAsyncFunctionComponent.kt +1 -1
  25. package/android/src/main/java/expo/modules/kotlin/functions/SuspendFunctionComponent.kt +1 -1
  26. package/android/src/main/java/expo/modules/kotlin/jni/CppType.kt +1 -0
  27. package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +5 -0
  28. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptFunction.kt +8 -1
  29. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +8 -0
  30. package/android/src/main/java/expo/modules/kotlin/types/ByteArrayTypeConverter.kt +13 -0
  31. package/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt +2 -0
  32. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +3 -0
  33. package/android/src/main/java/expo/modules/kotlin/types/UnitTypeConverter.kt +15 -0
  34. package/android/src/main/java/expo/modules/kotlin/types/folly/FollyDynamicExtensionConverter.kt +44 -0
  35. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +1 -1
  36. package/build/EventEmitter.d.ts.map +1 -1
  37. package/build/EventEmitter.js +0 -4
  38. package/build/EventEmitter.js.map +1 -1
  39. package/build/NativeModulesProxy.d.ts +4 -0
  40. package/build/NativeModulesProxy.d.ts.map +1 -1
  41. package/build/NativeModulesProxy.js +4 -0
  42. package/build/NativeModulesProxy.js.map +1 -1
  43. package/common/cpp/fabric/ExpoViewEventEmitter.h +1 -1
  44. package/common/cpp/fabric/ExpoViewProps.cpp +2 -1
  45. package/ios/Arguments/AnyArgument.swift +31 -5
  46. package/ios/Arguments/Convertible.swift +6 -0
  47. package/ios/Arguments/Enumerable.swift +6 -0
  48. package/ios/DynamicTypes/DynamicArrayType.swift +1 -1
  49. package/ios/DynamicTypes/DynamicDataType.swift +28 -0
  50. package/ios/DynamicTypes/DynamicDictionaryType.swift +65 -0
  51. package/ios/DynamicTypes/DynamicOptionalType.swift +1 -1
  52. package/ios/DynamicTypes/DynamicSharedObjectType.swift +2 -2
  53. package/ios/DynamicTypes/DynamicType.swift +15 -3
  54. package/ios/Fabric/ExpoFabricViewObjC.mm +0 -1
  55. package/ios/FileSystemUtilities/FileSystemUtilities.swift +100 -0
  56. package/ios/Interfaces/FileSystem/EXFileSystemInterface.h +0 -1
  57. package/ios/JSI/EXJSIConversions.mm +14 -0
  58. package/ios/JSI/EXJavaScriptRuntime.mm +35 -3
  59. package/ios/JSI/JavaScriptValue.swift +7 -1
  60. package/ios/Records/Field.swift +1 -1
  61. package/ios/SharedObjects/SharedObject.swift +6 -0
  62. package/ios/Tests/BlobConvertiblesSpec.swift +94 -0
  63. package/ios/Tests/ClassComponentSpec.swift +1 -1
  64. package/ios/Tests/ConstantsSpec.swift +1 -1
  65. package/ios/Tests/ConvertiblesSpec.swift +1 -1
  66. package/ios/Tests/CoreModuleSpec.swift +1 -1
  67. package/ios/Tests/DynamicTypeSpec.swift +1 -1
  68. package/ios/Tests/EitherSpec.swift +1 -1
  69. package/ios/Tests/EnumerableSpec.swift +1 -1
  70. package/ios/Tests/ExceptionsSpec.swift +2 -2
  71. package/ios/Tests/ExpoModulesSpec.swift +4 -4
  72. package/ios/Tests/ExpoRequestCdpInterceptorSpec.swift +4 -4
  73. package/ios/Tests/FunctionSpec.swift +7 -7
  74. package/ios/Tests/FunctionWithConvertiblesSpec.swift +1 -1
  75. package/ios/Tests/JavaScriptObjectSpec.swift +1 -1
  76. package/ios/Tests/JavaScriptRuntimeSpec.swift +1 -1
  77. package/ios/Tests/ModuleEventListenersSpec.swift +1 -1
  78. package/ios/Tests/ModuleRegistrySpec.swift +1 -1
  79. package/ios/Tests/PersistentFileLogSpec.swift +32 -34
  80. package/ios/Tests/PropertyComponentSpec.swift +1 -1
  81. package/ios/Tests/RecordSpec.swift +5 -5
  82. package/ios/Tests/SharedObjectRegistrySpec.swift +1 -1
  83. package/ios/Tests/SharedRefSpec.swift +1 -1
  84. package/ios/Tests/TypedArraysSpec.swift +1 -1
  85. package/ios/Tests/ViewDefinitionSpec.swift +1 -1
  86. package/ios/TypedArrays/AnyTypedArray.swift +7 -0
  87. package/ios/Views/ViewDefinition.swift +5 -1
  88. package/package.json +2 -2
  89. package/src/EventEmitter.ts +0 -3
  90. package/src/NativeModulesProxy.ts +5 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,28 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 1.11.0 — 2023-12-12
14
+
15
+ ### 🎉 New features
16
+
17
+ - Added support for React Native 0.73.0. ([#24971](https://github.com/expo/expo/pull/24971), [#25453](https://github.com/expo/expo/pull/25453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
18
+ - Added `Data <-> Uint8Array` convertible on iOS. ([#25726](https://github.com/expo/expo/pull/25726) by [@kudo](https://github.com/kudo))
19
+ - Added `ByteArray <-> Uint8Array` convertible on Android. ([#25727](https://github.com/expo/expo/pull/25727) by [@kudo](https://github.com/kudo))
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - [Android] Prevent the app from crashing during reloading when an unfinished promise tries to execute.
24
+ - [Android] Fix `JavaScriptFunction` not working when the return type wasn't provided. ([#25688](https://github.com/expo/expo/pull/25688) by [@lukmccall](https://github.com/lukmccall))
25
+ - [Android] Fix requesting only `WRITE_SETTINGS` rejecting promise even if the permission was granted. ([#25732](https://github.com/expo/expo/pull/25732) by [@lukmccall](https://github.com/lukmccall))
26
+ - [Android] Fix functions that are scheduled on the main thread weren't being called as soon as possible. ([#25757](https://github.com/expo/expo/pull/25757) by [@lukmccall](https://github.com/lukmccall))
27
+
28
+ ### 💡 Others
29
+
30
+ - [iOS] Made dynamic types creation faster. ([#25390](https://github.com/expo/expo/pull/25390) by [@tsapeta](https://github.com/tsapeta))
31
+ - [iOS] Add `FileSystemUtilities` to replace legacy interfaces. ([#25495](https://github.com/expo/expo/pull/25495) by [@alanhughes](https://github.com/alanjhughes))
32
+ - Bump C++ compiler setting to C++20. ([#25548](https://github.com/expo/expo/pull/25548) by [@kudo](https://github.com/kudo))
33
+ - Marked `NativeModulesProxy` as deprecated in favor of `requireNativeModule` and `requireOptionalNativeModule`. ([#25666](https://github.com/expo/expo/pull/25666) by [@tsapeta](https://github.com/tsapeta))
34
+
13
35
  ## 1.10.0 — 2023-11-14
14
36
 
15
37
  ### 🛠 Breaking changes
@@ -13,8 +13,8 @@ reactNativeMinorVersion = reactNativeVersion.split('.')[1].to_i
13
13
 
14
14
  fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
15
15
  fabric_compiler_flags = '-DRN_FABRIC_ENABLED -DRCT_NEW_ARCH_ENABLED'
16
- folly_version = '2021.07.22.00'
17
- folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
16
+ folly_version = '2022.05.16.00'
17
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -Wno-comma -Wno-shorten-64-to-32'
18
18
 
19
19
  Pod::Spec.new do |s|
20
20
  s.name = 'ExpoModulesCore'
@@ -46,7 +46,7 @@ Pod::Spec.new do |s|
46
46
  s.pod_target_xcconfig = {
47
47
  'USE_HEADERMAP' => 'YES',
48
48
  'DEFINES_MODULE' => 'YES',
49
- 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
49
+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
50
50
  'SWIFT_COMPILATION_MODE' => 'wholemodule',
51
51
  'HEADER_SEARCH_PATHS' => header_search_paths.join(' '),
52
52
  "FRAMEWORK_SEARCH_PATHS" => "\"${PODS_CONFIGURATION_BUILD_DIR}/React-hermes\"",
@@ -56,6 +56,7 @@ Pod::Spec.new do |s|
56
56
  '"${PODS_CONFIGURATION_BUILD_DIR}/ExpoModulesCore/Swift Compatibility Header"',
57
57
  '"$(PODS_ROOT)/Headers/Private/React-bridging/react/bridging"',
58
58
  '"$(PODS_CONFIGURATION_BUILD_DIR)/React-bridging/react_bridging.framework/Headers"',
59
+ '"$(PODS_ROOT)/Headers/Private/Yoga"',
59
60
  ]
60
61
  if fabric_enabled && ENV['USE_FRAMEWORKS']
61
62
  user_header_search_paths << "\"$(PODS_ROOT)/DoubleConversion\""
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.4.1)
3
3
  project(expo-modules-core)
4
4
 
5
5
  set(CMAKE_VERBOSE_MAKEFILE ON)
6
- set(CMAKE_CXX_STANDARD 17)
6
+ set(CMAKE_CXX_STANDARD 20)
7
7
  set(PACKAGE_NAME "expo-modules-core")
8
8
  set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
9
9
 
@@ -5,7 +5,7 @@ apply plugin: 'kotlin-android'
5
5
  apply plugin: 'maven-publish'
6
6
 
7
7
  group = 'host.exp.exponent'
8
- version = '1.10.0'
8
+ version = '1.11.0'
9
9
 
10
10
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
11
11
  if (expoModulesCorePlugin.exists()) {
@@ -143,7 +143,7 @@ android {
143
143
  defaultConfig {
144
144
  consumerProguardFiles 'proguard-rules.pro'
145
145
  versionCode 1
146
- versionName "1.10.0"
146
+ versionName "1.11.0"
147
147
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
148
148
 
149
149
  testInstrumentationRunner "expo.modules.TestRunner"
@@ -1,4 +1,3 @@
1
-
2
1
  -keepclassmembers class * {
3
2
  @expo.modules.core.interfaces.ExpoProp *;
4
3
  }
@@ -16,7 +15,12 @@
16
15
  *;
17
16
  }
18
17
  -keepclassmembers enum * implements expo.modules.kotlin.types.Enumerable {
19
- *;
18
+ *;
19
+ }
20
+
21
+ -keep,allowoptimization,allowobfuscation class * extends expo.modules.kotlin.modules.Module {
22
+ public <init>();
23
+ public expo.modules.kotlin.modules.ModuleDefinitionData definition();
20
24
  }
21
25
 
22
26
  -keepclassmembers class * implements expo.modules.kotlin.views.ExpoView {
@@ -12,7 +12,7 @@ add_library(fabric STATIC
12
12
  include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
13
13
 
14
14
  target_compile_options(fabric PRIVATE
15
- "-std=c++17"
15
+ "-std=c++20"
16
16
  ${folly_FLAGS}
17
17
  )
18
18
 
@@ -43,6 +43,7 @@ target_link_libraries(fabric
43
43
  ReactAndroid::react_render_debug
44
44
  ReactAndroid::react_render_graphics
45
45
  ReactAndroid::react_render_mapbuffer
46
+ ReactAndroid::react_utils
46
47
  ReactAndroid::rrc_view
47
48
  ReactAndroid::runtimeexecutor
48
49
  ReactAndroid::yoga
@@ -30,6 +30,7 @@ void JSIInteropModuleRegistry::registerNatives() {
30
30
  makeNativeMethod("global", JSIInteropModuleRegistry::global),
31
31
  makeNativeMethod("createObject", JSIInteropModuleRegistry::createObject),
32
32
  makeNativeMethod("drainJSEventLoop", JSIInteropModuleRegistry::drainJSEventLoop),
33
+ makeNativeMethod("wasDeallocated", JSIInteropModuleRegistry::jniWasDeallocated),
33
34
  });
34
35
  }
35
36
 
@@ -188,4 +189,8 @@ void JSIInteropModuleRegistry::registerSharedObject(
188
189
  );
189
190
  method(javaPart_, std::move(native), std::move(js));
190
191
  }
192
+
193
+ void JSIInteropModuleRegistry::jniWasDeallocated() {
194
+ wasDeallocated = true;
195
+ }
191
196
  } // namespace expo
@@ -109,6 +109,8 @@ public:
109
109
  std::shared_ptr<JavaScriptRuntime> runtimeHolder;
110
110
  std::unique_ptr<JSReferencesCache> jsRegistry;
111
111
  jni::global_ref<JNIDeallocator::javaobject> jniDeallocator;
112
+
113
+ bool wasDeallocated = false;
112
114
  private:
113
115
  friend HybridBase;
114
116
  jni::global_ref<JSIInteropModuleRegistry::javaobject> javaPart_;
@@ -123,5 +125,7 @@ private:
123
125
  inline jni::local_ref<JavaScriptModuleObject::javaobject> callGetCoreModuleObject() const;
124
126
 
125
127
  inline bool callHasModule(const std::string &moduleName) const;
128
+
129
+ void jniWasDeallocated();
126
130
  };
127
131
  } // namespace expo
@@ -71,6 +71,10 @@ jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction(
71
71
  }
72
72
 
73
73
  jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
74
+ auto enhancedArg = decorateValueForDynamicExtension(strongWrapper2->runtime(), arg);
75
+ if (enhancedArg) {
76
+ arg = std::move(*enhancedArg);
77
+ }
74
78
  if (!isRejectCallback) {
75
79
  strongWrapper2->callback().call(
76
80
  strongWrapper2->runtime(),
@@ -308,6 +312,15 @@ jsi::Function MethodMetadata::toAsyncFunction(
308
312
  const jsi::Value *args,
309
313
  size_t count
310
314
  ) -> jsi::Value {
315
+ /**
316
+ * Halt execution during cleaning phase as modules and js context will be deallocated soon.
317
+ * The output of this method doesn't matter.
318
+ * We added that check to prevent the app from crashing when users reload their apps.
319
+ */
320
+ if (moduleRegistry->wasDeallocated) {
321
+ return jsi::Value::undefined();
322
+ }
323
+
311
324
  JNIEnv *env = jni::Environment::current();
312
325
 
313
326
  /**
@@ -19,13 +19,14 @@ enum CppType {
19
19
  JS_VALUE = 1 << 7,
20
20
  READABLE_ARRAY = 1 << 8,
21
21
  READABLE_MAP = 1 << 9,
22
- TYPED_ARRAY = 1 << 10,
23
- PRIMITIVE_ARRAY = 1 << 11,
24
- LIST = 1 << 12,
25
- MAP = 1 << 13,
26
- VIEW_TAG = 1 << 14,
27
- SHARED_OBJECT_ID = 1 << 15,
28
- JS_FUNCTION = 1 << 16,
29
- ANY = 1 << 17
22
+ UINT8_TYPED_ARRAY = 1 << 10,
23
+ TYPED_ARRAY = 1 << 11,
24
+ PRIMITIVE_ARRAY = 1 << 12,
25
+ LIST = 1 << 13,
26
+ MAP = 1 << 14,
27
+ VIEW_TAG = 1 << 15,
28
+ SHARED_OBJECT_ID = 1 << 16,
29
+ JS_FUNCTION = 1 << 17,
30
+ ANY = 1 << 18
30
31
  };
31
32
  } // namespace expo
@@ -70,6 +70,9 @@ std::string ExpectedType::getJClassString(bool allowsPrimitives) {
70
70
  if (type == CppType::READABLE_MAP) {
71
71
  return "com/facebook/react/bridge/ReadableNativeMap";
72
72
  }
73
+ if (type == CppType::UINT8_TYPED_ARRAY) {
74
+ return "[B";
75
+ }
73
76
  if (type == CppType::TYPED_ARRAY) {
74
77
  return "expo/modules/kotlin/jni/JavaScriptTypedArray";
75
78
  }
@@ -153,6 +153,33 @@ bool ReadableNativeMapArrayFrontendConverter::canConvert(
153
153
  return value.isObject();
154
154
  }
155
155
 
156
+ jobject ByteArrayFrontendConverter::convert(
157
+ jsi::Runtime &rt,
158
+ JNIEnv *env,
159
+ JSIInteropModuleRegistry *moduleRegistry,
160
+ const jsi::Value &value
161
+ ) const {
162
+ auto typedArray = TypedArray(rt, value.getObject(rt));
163
+ size_t length = typedArray.byteLength(rt);
164
+ auto byteArray = jni::JArrayByte::newArray(length);
165
+ byteArray->setRegion(0, length, static_cast<const signed char *>(typedArray.getRawPointer(rt)));
166
+ return byteArray.release();
167
+ }
168
+
169
+ bool ByteArrayFrontendConverter::canConvert(
170
+ jsi::Runtime &rt,
171
+ const jsi::Value &value
172
+ ) const {
173
+ if (value.isObject()) {
174
+ auto object = value.getObject(rt);
175
+ if (isTypedArray(rt, object)) {
176
+ auto typedArray = TypedArray(rt, object);
177
+ return typedArray.getKind(rt) == TypedArrayKind::Uint8Array;
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+
156
183
  jobject TypedArrayFrontendConverter::convert(
157
184
  jsi::Runtime &rt,
158
185
  JNIEnv *env,
@@ -160,6 +160,21 @@ public:
160
160
  bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
161
161
  };
162
162
 
163
+ /**
164
+ * Converter from js Uint8Array to [java.lang.Byte] array.
165
+ */
166
+ class ByteArrayFrontendConverter : public FrontendConverter {
167
+ public:
168
+ jobject convert(
169
+ jsi::Runtime &rt,
170
+ JNIEnv *env,
171
+ JSIInteropModuleRegistry *moduleRegistry,
172
+ const jsi::Value &value
173
+ ) const override;
174
+
175
+ bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
176
+ };
177
+
163
178
  /**
164
179
  * Converter from js type array to [expo.modules.kotlin.jni.JavaScriptTypedArray].
165
180
  */
@@ -16,6 +16,7 @@ void FrontendConverterProvider::createConverters() {
16
16
  RegisterConverter(CppType::FLOAT, FloatFrontendConverter);
17
17
  RegisterConverter(CppType::DOUBLE, DoubleFrontendConverter);
18
18
  RegisterConverter(CppType::BOOLEAN, BooleanFrontendConverter);
19
+ RegisterConverter(CppType::UINT8_TYPED_ARRAY, ByteArrayFrontendConverter);
19
20
  RegisterConverter(CppType::TYPED_ARRAY, TypedArrayFrontendConverter);
20
21
  RegisterConverter(CppType::JS_OBJECT, JavaScriptObjectFrontendConverter);
21
22
  RegisterConverter(CppType::JS_VALUE, JavaScriptValueFrontendConverter);
@@ -11,6 +11,44 @@
11
11
 
12
12
  namespace react = facebook::react;
13
13
 
14
+ namespace {
15
+
16
+ // This value should be synced with the value in **FollyDynamicExtensionConverter.kt**
17
+ constexpr char DYNAMIC_EXTENSION_PREFIX[] = "__expo_dynamic_extension__#";
18
+
19
+ /**
20
+ * Create an JavaScript Uint8Array instance from Java ByteArray.
21
+ */
22
+ jsi::Value createUint8Array(jsi::Runtime &rt, jni::alias_ref<jni::JArrayByte> byteArray) {
23
+ auto arrayBufferCtor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer");
24
+ auto arrayBufferObject = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(byteArray->size())).getObject(rt);
25
+ auto arrayBuffer = arrayBufferObject.getArrayBuffer(rt);
26
+ byteArray->getRegion(0, byteArray->size(), reinterpret_cast<signed char *>(arrayBuffer.data(rt)));
27
+
28
+ auto uint8ArrayCtor = rt.global().getPropertyAsFunction(rt, "Uint8Array");
29
+ auto uint8Array = uint8ArrayCtor.callAsConstructor(rt, arrayBufferObject).getObject(rt);
30
+ return uint8Array;
31
+ }
32
+
33
+ /**
34
+ * Convert a string with FollyDynamicExtensionConverter support.
35
+ */
36
+ std::optional<jsi::Value> convertStringToFollyDynamicIfNeeded(jsi::Runtime &rt, const std::string& string) {
37
+ if (!string.starts_with(DYNAMIC_EXTENSION_PREFIX)) {
38
+ return std::nullopt;
39
+ }
40
+ auto converterClass = jni::findClassLocal("expo/modules/kotlin/types/folly/FollyDynamicExtensionConverter");
41
+ const auto getInstanceMethod = converterClass->getStaticMethod<jni::JObject(std::string)>("get");
42
+ jni::local_ref<jni::JObject> instance = getInstanceMethod(converterClass, string);
43
+
44
+ if (instance->isInstanceOf(jni::JArrayByte::javaClassStatic())) {
45
+ return createUint8Array(rt, jni::static_ref_cast<jni::JArrayByte>(instance));
46
+ }
47
+ return std::nullopt;
48
+ }
49
+
50
+ } // namespace
51
+
14
52
  namespace expo {
15
53
 
16
54
  jsi::Value convert(
@@ -34,10 +72,9 @@ jsi::Value convert(
34
72
  return {(double) jni::static_ref_cast<jni::JLong>(value)->value()};
35
73
  }
36
74
  if (env->IsInstanceOf(unpackedValue, cache->getJClass("java/lang/String").clazz)) {
37
- return jsi::String::createFromUtf8(
38
- rt,
39
- jni::static_ref_cast<jni::JString>(value)->toStdString()
40
- );
75
+ std::string string = jni::static_ref_cast<jni::JString>(value)->toStdString();
76
+ auto enhancedValue = convertStringToFollyDynamicIfNeeded(rt, string);
77
+ return enhancedValue ? std::move(*enhancedValue) : jsi::String::createFromUtf8(rt, string);
41
78
  }
42
79
  if (env->IsInstanceOf(unpackedValue, cache->getJClass("java/lang/Boolean").clazz)) {
43
80
  return {(bool) jni::static_ref_cast<jni::JBoolean>(value)->value()};
@@ -52,7 +89,12 @@ jsi::Value convert(
52
89
  auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(value)
53
90
  ->cthis()
54
91
  ->consume();
55
- return jsi::valueFromDynamic(rt, dynamic);
92
+ auto arg = jsi::valueFromDynamic(rt, dynamic);
93
+ auto enhancedArg = decorateValueForDynamicExtension(rt, arg);
94
+ if (enhancedArg) {
95
+ arg = std::move(*enhancedArg);
96
+ }
97
+ return arg;
56
98
  }
57
99
  if (env->IsInstanceOf(
58
100
  unpackedValue,
@@ -61,7 +103,12 @@ jsi::Value convert(
61
103
  auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(value)
62
104
  ->cthis()
63
105
  ->consume();
64
- return jsi::valueFromDynamic(rt, dynamic);
106
+ auto arg = jsi::valueFromDynamic(rt, dynamic);
107
+ auto enhancedArg = decorateValueForDynamicExtension(rt, arg);
108
+ if (enhancedArg) {
109
+ arg = std::move(*enhancedArg);
110
+ }
111
+ return arg;
65
112
  }
66
113
  if (env->IsInstanceOf(unpackedValue, JavaScriptModuleObject::javaClassStatic().get())) {
67
114
  auto anonymousObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(value)
@@ -106,4 +153,44 @@ jsi::Value convert(
106
153
 
107
154
  return jsi::Value::undefined();
108
155
  }
156
+
157
+ std::optional<jsi::Value> decorateValueForDynamicExtension(jsi::Runtime &rt, const jsi::Value &value) {
158
+ if (value.isString()) {
159
+ std::string string = value.getString(rt).utf8(rt);
160
+ return convertStringToFollyDynamicIfNeeded(rt, string);
161
+ }
162
+
163
+ if (value.isObject()) {
164
+ auto jsObject = value.getObject(rt);
165
+ if (jsObject.isArray(rt)) {
166
+ bool changed = false;
167
+ auto jsArray = jsObject.getArray(rt);
168
+ size_t length = jsArray.length(rt);
169
+ for (size_t i = 0; i < length; ++i) {
170
+ auto converted = decorateValueForDynamicExtension(rt, jsArray.getValueAtIndex(rt, i));
171
+ if (converted) {
172
+ jsArray.setValueAtIndex(rt, i, std::move(*converted));
173
+ changed = true;
174
+ }
175
+ }
176
+ return changed ? std::make_optional<jsi::Value>(std::move(jsArray)) : std::nullopt;
177
+ } else {
178
+ bool changed = false;
179
+ auto propNames = jsObject.getPropertyNames(rt);
180
+ size_t length = propNames.length(rt);
181
+ for (size_t i = 0; i < length; ++i) {
182
+ auto propName = propNames.getValueAtIndex(rt, i).getString(rt);
183
+ auto converted = decorateValueForDynamicExtension(rt, jsObject.getProperty(rt, propName));
184
+ if (converted) {
185
+ jsObject.setProperty(rt, propName, std::move(*converted));
186
+ changed = true;
187
+ }
188
+ }
189
+ return changed ? std::make_optional<jsi::Value>(std::move(jsObject)) : std::nullopt;
190
+ }
191
+ }
192
+
193
+ return std::nullopt;
194
+ }
195
+
109
196
  } // namespace expo
@@ -6,6 +6,7 @@
6
6
 
7
7
  #include <fbjni/fbjni.h>
8
8
  #include <jsi/jsi.h>
9
+ #include <optional>
9
10
 
10
11
  namespace jni = facebook::jni;
11
12
  namespace jsi = facebook::jsi;
@@ -19,4 +20,9 @@ jsi::Value convert(
19
20
  jni::local_ref<jobject> value
20
21
  );
21
22
 
23
+ /**
24
+ * Decorate jsi::Value with FollyDynamicExtensionConverter support.
25
+ */
26
+ std::optional<jsi::Value> decorateValueForDynamicExtension(jsi::Runtime &rt, const jsi::Value &value);
27
+
22
28
  } // namespace expo
@@ -64,7 +64,7 @@ open class PermissionsService(val context: Context) : InternalModule, Permission
64
64
  getPermissions(
65
65
  PermissionsResponseListener { permissionsMap: MutableMap<String, PermissionsResponse> ->
66
66
  val areAllGranted = permissionsMap.all { (_, response) -> response.status == PermissionsStatus.GRANTED }
67
- val areAllDenied = permissionsMap.all { (_, response) -> response.status == PermissionsStatus.DENIED }
67
+ val areAllDenied = permissionsMap.isNotEmpty() && permissionsMap.all { (_, response) -> response.status == PermissionsStatus.DENIED }
68
68
  val canAskAgain = permissionsMap.all { (_, response) -> response.canAskAgain }
69
69
 
70
70
  promise.resolve(
@@ -113,6 +113,11 @@ open class PermissionsService(val context: Context) : InternalModule, Permission
113
113
 
114
114
  @Throws(IllegalStateException::class)
115
115
  override fun askForPermissions(responseListener: PermissionsResponseListener, vararg permissions: String) {
116
+ if (permissions.isEmpty()) {
117
+ responseListener.onResult(mutableMapOf())
118
+ return
119
+ }
120
+
116
121
  if (permissions.contains(Manifest.permission.WRITE_SETTINGS)) {
117
122
  val permissionsToAsk = permissions.toMutableList().apply { remove(Manifest.permission.WRITE_SETTINGS) }.toTypedArray()
118
123
  val newListener = PermissionsResponseListener {
@@ -136,6 +141,11 @@ open class PermissionsService(val context: Context) : InternalModule, Permission
136
141
  addToAskedPermissionsCache(arrayOf(Manifest.permission.WRITE_SETTINGS))
137
142
  askForWriteSettingsPermissionFirst()
138
143
  } else {
144
+ // User only ask for `WRITE_SETTINGS`, we can already return response
145
+ if (permissionsToAsk.isEmpty()) {
146
+ newListener.onResult(mutableMapOf())
147
+ return
148
+ }
139
149
  askForManifestPermissions(permissionsToAsk, newListener)
140
150
  }
141
151
  } else {
@@ -295,6 +295,9 @@ class AppContext(
295
295
  modulesQueue.cancel(ContextDestroyedException())
296
296
  mainQueue.cancel(ContextDestroyedException())
297
297
  backgroundCoroutineScope.cancel(ContextDestroyedException())
298
+ if (::jsiInterop.isInitialized) {
299
+ jsiInterop.wasDeallocated()
300
+ }
298
301
  jniDeallocator.deallocate()
299
302
  logger.info("✅ AppContext was destroyed")
300
303
  }
@@ -15,7 +15,7 @@ import expo.modules.kotlin.tracing.trace
15
15
  import kotlinx.coroutines.launch
16
16
  import kotlin.reflect.KClass
17
17
 
18
- class ModuleHolder(val module: Module) {
18
+ class ModuleHolder<T : Module>(val module: T) {
19
19
  val definition = module.definition()
20
20
 
21
21
  val name get() = definition.name
@@ -12,11 +12,11 @@ import java.lang.ref.WeakReference
12
12
 
13
13
  class ModuleRegistry(
14
14
  private val appContext: WeakReference<AppContext>
15
- ) : Iterable<ModuleHolder> {
15
+ ) : Iterable<ModuleHolder<*>> {
16
16
  @PublishedApi
17
- internal val registry = mutableMapOf<String, ModuleHolder>()
17
+ internal val registry = mutableMapOf<String, ModuleHolder<*>>()
18
18
 
19
- fun register(module: Module) = trace("ModuleRegistry.register(${module.javaClass})") {
19
+ fun <T : Module> register(module: T) = trace("ModuleRegistry.register(${module.javaClass})") {
20
20
  module._appContext = requireNotNull(appContext.get()) { "Cannot create a module for invalid app context." }
21
21
 
22
22
  val holder = ModuleHolder(module)
@@ -56,12 +56,13 @@ class ModuleRegistry(
56
56
  return registry.values.find { it.module is T }?.module as? T
57
57
  }
58
58
 
59
- fun getModuleHolder(name: String): ModuleHolder? = registry[name]
59
+ fun getModuleHolder(name: String): ModuleHolder<*>? = registry[name]
60
60
 
61
- fun getModuleHolder(module: Module): ModuleHolder? =
62
- registry.values.find { it.module === module }
61
+ @Suppress("UNCHECKED_CAST")
62
+ fun <T : Module> getModuleHolder(module: T): ModuleHolder<T>? =
63
+ registry.values.find { it.module === module } as? ModuleHolder<T>
63
64
 
64
- fun <T : View> getModuleHolder(viewClass: Class<T>): ModuleHolder? {
65
+ fun <T : View> getModuleHolder(viewClass: Class<T>): ModuleHolder<*>? {
65
66
  return registry.firstNotNullOfOrNull { (_, holder) ->
66
67
  if (holder.definition.viewManagerDefinition?.viewType == viewClass) {
67
68
  holder
@@ -91,7 +92,7 @@ class ModuleRegistry(
91
92
  }
92
93
  }
93
94
 
94
- override fun iterator(): Iterator<ModuleHolder> = registry.values.iterator()
95
+ override fun iterator(): Iterator<ModuleHolder<*>> = registry.values.iterator()
95
96
 
96
97
  fun cleanUp() {
97
98
  registry.clear()
@@ -17,7 +17,7 @@ import java.lang.ref.WeakReference
17
17
  * But because of that, we had to create a wrapper for EventEmitter.
18
18
  */
19
19
  class KModuleEventEmitterWrapper(
20
- private val moduleHolder: ModuleHolder,
20
+ private val moduleHolder: ModuleHolder<*>,
21
21
  legacyEventEmitter: expo.modules.core.interfaces.services.EventEmitter,
22
22
  reactContextHolder: WeakReference<ReactApplicationContext>
23
23
  ) : KEventEmitterWrapper(legacyEventEmitter, reactContextHolder) {
@@ -50,7 +50,7 @@ abstract class AnyFunction(
50
50
  /**
51
51
  * A minimum number of arguments the functions needs which equals to `argumentsCount` reduced by the number of trailing optional arguments.
52
52
  */
53
- internal val requiredArgumentsCount = run {
53
+ private val requiredArgumentsCount = run {
54
54
  val nonNullableArgIndex = desiredArgsTypes
55
55
  .reversed()
56
56
  .indexOfFirst { !it.kType.isMarkedNullable }
@@ -1,5 +1,6 @@
1
1
  package expo.modules.kotlin.functions
2
2
 
3
+ import android.view.View
3
4
  import com.facebook.react.bridge.ReadableArray
4
5
  import expo.modules.BuildConfig
5
6
  import expo.modules.kotlin.AppContext
@@ -22,7 +23,7 @@ abstract class AsyncFunction(
22
23
  desiredArgsTypes: Array<AnyType>
23
24
  ) : BaseAsyncFunctionComponent(name, desiredArgsTypes) {
24
25
 
25
- override fun call(holder: ModuleHolder, args: ReadableArray, promise: Promise) {
26
+ override fun call(holder: ModuleHolder<*>, args: ReadableArray, promise: Promise) {
26
27
  val queue = when (queue) {
27
28
  Queues.MAIN -> holder.module.appContext.mainQueue
28
29
  Queues.DEFAULT -> null
@@ -85,24 +86,32 @@ abstract class AsyncFunction(
85
86
  }
86
87
  }
87
88
 
88
- if (queue == Queues.MAIN) {
89
- if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
89
+ dispatchOnQueue(appContext, functionBody)
90
+ }
91
+ }
92
+
93
+ private fun dispatchOnQueue(appContext: AppContext, block: () -> Unit) {
94
+ when (queue) {
95
+ Queues.DEFAULT -> {
96
+ appContext.modulesQueue.launch {
97
+ block()
98
+ }
99
+ }
100
+
101
+ Queues.MAIN -> {
102
+ if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && desiredArgsTypes.any { it.inheritFrom<View>() }) {
90
103
  // On certain occasions, invoking a function on a view could lead to an error
91
104
  // because of the asynchronous communication between the JavaScript and native components.
92
105
  // In such cases, the native view may not have been mounted yet,
93
106
  // but the JavaScript code has already received the future tag of the view.
94
107
  // To avoid this issue, we have decided to temporarily utilize
95
108
  // the UIManagerModule for dispatching functions on the main thread.
96
- appContext.dispatchOnMainUsingUIManager(functionBody)
97
- return@registerAsyncFunction
109
+ appContext.dispatchOnMainUsingUIManager(block)
110
+ return
98
111
  }
99
112
 
100
113
  appContext.mainQueue.launch {
101
- functionBody()
102
- }
103
- } else {
104
- appContext.modulesQueue.launch {
105
- functionBody()
114
+ block()
106
115
  }
107
116
  }
108
117
  }
@@ -17,7 +17,7 @@ abstract class BaseAsyncFunctionComponent(
17
17
 
18
18
  protected var queue = Queues.DEFAULT
19
19
 
20
- abstract fun call(holder: ModuleHolder, args: ReadableArray, promise: Promise)
20
+ abstract fun call(holder: ModuleHolder<*>, args: ReadableArray, promise: Promise)
21
21
 
22
22
  fun runOnQueue(queue: Queues) = apply {
23
23
  this.queue = queue
@@ -22,7 +22,7 @@ class SuspendFunctionComponent(
22
22
  private val body: suspend CoroutineScope.(args: Array<out Any?>) -> Any?
23
23
  ) : BaseAsyncFunctionComponent(name, desiredArgsTypes) {
24
24
 
25
- override fun call(holder: ModuleHolder, args: ReadableArray, promise: Promise) {
25
+ override fun call(holder: ModuleHolder<*>, args: ReadableArray, promise: Promise) {
26
26
  val appContext = holder.module.appContext
27
27
  val queue = when (queue) {
28
28
  Queues.MAIN -> appContext.mainQueue
@@ -24,6 +24,7 @@ enum class CppType(val clazz: KClass<*>, val value: Int = nextValue()) {
24
24
  JS_VALUE(JavaScriptValue::class),
25
25
  READABLE_ARRAY(ReadableArray::class),
26
26
  READABLE_MAP(ReadableMap::class),
27
+ UINT8_TYPED_ARRAY(ByteArray::class),
27
28
  TYPED_ARRAY(TypedArray::class),
28
29
  PRIMITIVE_ARRAY(Array::class),
29
30
  LIST(List::class),
@@ -68,6 +68,11 @@ class JSIInteropModuleRegistry(appContext: AppContext) : Destructible {
68
68
  */
69
69
  external fun drainJSEventLoop()
70
70
 
71
+ /**
72
+ * Informs C++ that runtime was deallocated.
73
+ */
74
+ external fun wasDeallocated()
75
+
71
76
  /**
72
77
  * Returns a `JavaScriptModuleObject` that is a bridge between [expo.modules.kotlin.modules.Module]
73
78
  * and HostObject exported via JSI.