expo-modules-core 1.2.7 → 1.3.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 (180) hide show
  1. package/CHANGELOG.md +28 -5
  2. package/ExpoModulesCore.podspec +1 -0
  3. package/README.md +1 -1
  4. package/android/ExpoModulesCorePlugin.gradle +16 -0
  5. package/android/build.gradle +3 -2
  6. package/android/src/main/cpp/Exceptions.cpp +8 -0
  7. package/android/src/main/cpp/Exceptions.h +11 -0
  8. package/android/src/main/cpp/ExpoModulesHostObject.cpp +22 -5
  9. package/android/src/main/cpp/ExpoModulesHostObject.h +5 -0
  10. package/android/src/main/cpp/JNIInjector.cpp +2 -0
  11. package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +25 -1
  12. package/android/src/main/cpp/JSIInteropModuleRegistry.h +14 -0
  13. package/android/src/main/cpp/JSIObjectWrapper.h +15 -4
  14. package/android/src/main/cpp/JSITypeConverter.h +3 -2
  15. package/android/src/main/cpp/JavaReferencesCache.cpp +2 -0
  16. package/android/src/main/cpp/JavaScriptFunction.cpp +56 -0
  17. package/android/src/main/cpp/JavaScriptFunction.h +54 -0
  18. package/android/src/main/cpp/JavaScriptModuleObject.cpp +225 -105
  19. package/android/src/main/cpp/JavaScriptModuleObject.h +67 -34
  20. package/android/src/main/cpp/JavaScriptObject.cpp +55 -1
  21. package/android/src/main/cpp/JavaScriptObject.h +17 -13
  22. package/android/src/main/cpp/JavaScriptRuntime.cpp +12 -3
  23. package/android/src/main/cpp/JavaScriptRuntime.h +9 -1
  24. package/android/src/main/cpp/JavaScriptValue.cpp +9 -0
  25. package/android/src/main/cpp/JavaScriptValue.h +4 -0
  26. package/android/src/main/cpp/MethodMetadata.cpp +66 -87
  27. package/android/src/main/cpp/MethodMetadata.h +18 -16
  28. package/android/src/main/cpp/ObjectDeallocator.h +25 -0
  29. package/android/src/main/cpp/WeakRuntimeHolder.cpp +7 -0
  30. package/android/src/main/cpp/WeakRuntimeHolder.h +4 -0
  31. package/android/src/main/cpp/types/CppType.h +4 -1
  32. package/android/src/main/cpp/types/FrontendConverter.cpp +58 -0
  33. package/android/src/main/cpp/types/FrontendConverter.h +45 -0
  34. package/android/src/main/cpp/types/FrontendConverterProvider.cpp +3 -0
  35. package/android/src/main/cpp/types/JNIToJSIConverter.cpp +88 -0
  36. package/android/src/main/cpp/types/JNIToJSIConverter.h +22 -0
  37. package/android/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMapHelper.kt +10 -0
  38. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +8 -25
  39. package/android/src/main/java/expo/modules/kotlin/FilteredIterator.kt +37 -0
  40. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +1 -1
  41. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +30 -23
  42. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +0 -3
  43. package/android/src/main/java/expo/modules/kotlin/Utils.kt +21 -0
  44. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultCaller.kt +21 -1
  45. package/android/src/main/java/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +112 -0
  46. package/android/src/main/java/expo/modules/kotlin/classcomponent/ClassDefinitionData.kt +10 -0
  47. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +21 -0
  48. package/android/src/main/java/expo/modules/kotlin/exception/CommonExceptions.kt +15 -0
  49. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +17 -3
  50. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +38 -8
  51. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionComponent.kt +3 -2
  52. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromiseComponent.kt +3 -2
  53. package/android/src/main/java/expo/modules/kotlin/functions/SuspendFunctionComponent.kt +1 -0
  54. package/android/src/main/java/expo/modules/kotlin/functions/SyncFunctionComponent.kt +18 -11
  55. package/android/src/main/java/expo/modules/kotlin/jni/CppType.kt +4 -1
  56. package/android/src/main/java/expo/modules/kotlin/jni/JNIDeallocator.kt +73 -0
  57. package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +28 -2
  58. package/android/src/main/java/expo/modules/kotlin/jni/JavaCallback.kt +8 -1
  59. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptFunction.kt +48 -0
  60. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +40 -3
  61. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptObject.kt +23 -3
  62. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptValue.kt +26 -1
  63. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +0 -11
  64. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +26 -16
  65. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +3 -1
  66. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt +12 -0
  67. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +62 -0
  68. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectTypeConverter.kt +27 -0
  69. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +2 -1
  70. package/android/src/main/java/expo/modules/kotlin/types/EitherTypeConverter.kt +7 -6
  71. package/android/src/main/java/expo/modules/kotlin/types/JavaScriptFunctionTypeConverter.kt +22 -0
  72. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +30 -24
  73. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +45 -1
  74. package/android/src/main/java/expo/modules/kotlin/types/TypedArrayTypeConverter.kt +3 -2
  75. package/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +3 -1
  76. package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +3 -3
  77. package/android/src/main/java/expo/modules/kotlin/views/FilteredReadableMap.kt +53 -0
  78. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +25 -5
  79. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +25 -5
  80. package/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +161 -10
  81. package/android/src/main/java/expo/modules/kotlin/views/ViewGroupDefinitionBuilder.kt +0 -67
  82. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +6 -7
  83. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +40 -3
  84. package/android/src/main/java/expo/modules/kotlin/views/ViewTypeConverter.kt +44 -0
  85. package/android-annotation/build.gradle +45 -0
  86. package/android-annotation/src/main/java/expo/modules/annotation/Config.kt +7 -0
  87. package/android-annotation/src/main/java/expo/modules/annotation/ConverterBinder.kt +7 -0
  88. package/android-annotation-processor/build.gradle +51 -0
  89. package/android-annotation-processor/src/main/java/expo/modules/annotationprocessor/ExpoSymbolProcessor.kt +175 -0
  90. package/android-annotation-processor/src/main/java/expo/modules/annotationprocessor/ExpoSymbolProcessorProvider.kt +10 -0
  91. package/android-annotation-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +1 -0
  92. package/build/NativeViewManagerAdapter.native.d.ts.map +1 -1
  93. package/build/NativeViewManagerAdapter.native.js +36 -23
  94. package/build/NativeViewManagerAdapter.native.js.map +1 -1
  95. package/build/requireNativeModule.js +2 -2
  96. package/build/requireNativeModule.js.map +1 -1
  97. package/common/cpp/fabric/ExpoViewProps.cpp +18 -3
  98. package/common/cpp/fabric/ExpoViewProps.h +4 -1
  99. package/ios/Fabric/ExpoFabricView.swift +10 -10
  100. package/ios/Fabric/ExpoFabricViewObjC.h +2 -0
  101. package/ios/Fabric/ExpoFabricViewObjC.mm +17 -2
  102. package/ios/JSI/EXJSIInstaller.mm +1 -1
  103. package/ios/JSI/EXJSIUtils.h +5 -0
  104. package/ios/JSI/EXJSIUtils.mm +17 -0
  105. package/ios/JSI/EXJavaScriptRuntime.h +5 -0
  106. package/ios/JSI/EXJavaScriptRuntime.mm +6 -0
  107. package/ios/JSI/EXJavaScriptValue.h +2 -0
  108. package/ios/JSI/EXJavaScriptValue.mm +8 -0
  109. package/ios/JSI/EXJavaScriptWeakObject.mm +29 -8
  110. package/ios/JSI/EXRawJavaScriptFunction.h +24 -0
  111. package/ios/JSI/EXRawJavaScriptFunction.mm +52 -0
  112. package/ios/JSI/ExpoModulesHostObject.mm +1 -1
  113. package/ios/JSI/JavaScriptValue.swift +28 -1
  114. package/ios/ModuleRegistry/EXModuleRegistry.h +0 -4
  115. package/ios/ModuleRegistry/EXModuleRegistry.m +0 -23
  116. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +0 -16
  117. package/ios/ModuleRegistryProvider/EXModuleRegistryProvider.m +0 -6
  118. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +1 -31
  119. package/ios/Swift/AppContext.swift +46 -6
  120. package/ios/Swift/Arguments/Convertible.swift +3 -3
  121. package/ios/Swift/Arguments/Convertibles.swift +5 -5
  122. package/ios/Swift/Classes/ClassComponent.swift +18 -12
  123. package/ios/Swift/Classes/ClassRegistry.swift +31 -0
  124. package/ios/Swift/Conversions.swift +19 -3
  125. package/ios/Swift/Convertibles/Convertibles+Color.swift +3 -3
  126. package/ios/Swift/Convertibles/Either.swift +6 -4
  127. package/ios/Swift/DynamicTypes/AnyDynamicType.swift +18 -2
  128. package/ios/Swift/DynamicTypes/DynamicArrayType.swift +3 -3
  129. package/ios/Swift/DynamicTypes/DynamicConvertibleType.swift +2 -2
  130. package/ios/Swift/DynamicTypes/DynamicEnumType.swift +1 -1
  131. package/ios/Swift/DynamicTypes/DynamicJavaScriptType.swift +27 -0
  132. package/ios/Swift/DynamicTypes/DynamicOptionalType.swift +9 -2
  133. package/ios/Swift/DynamicTypes/DynamicRawType.swift +1 -1
  134. package/ios/Swift/DynamicTypes/DynamicSharedObjectType.swift +16 -2
  135. package/ios/Swift/DynamicTypes/DynamicType.swift +6 -0
  136. package/ios/Swift/DynamicTypes/DynamicTypedArrayType.swift +15 -4
  137. package/ios/Swift/DynamicTypes/DynamicViewType.swift +68 -0
  138. package/ios/Swift/ExpoBridgeModule.swift +1 -1
  139. package/ios/Swift/Functions/AnyFunction.swift +5 -4
  140. package/ios/Swift/Functions/AsyncFunctionComponent.swift +22 -19
  141. package/ios/Swift/Functions/ConcurrentFunctionDefinition.swift +29 -13
  142. package/ios/Swift/Functions/SyncFunctionComponent.swift +26 -15
  143. package/ios/Swift/JavaScriptFunction.swift +68 -0
  144. package/ios/Swift/JavaScriptUtils.swift +57 -18
  145. package/ios/Swift/ModuleHolder.swift +22 -10
  146. package/ios/Swift/Modules/ModuleDefinition.swift +8 -2
  147. package/ios/Swift/Objects/JavaScriptObjectBuilder.swift +8 -8
  148. package/ios/Swift/Objects/ObjectDefinition.swift +17 -15
  149. package/ios/Swift/Objects/PropertyComponent.swift +23 -17
  150. package/ios/Swift/Records/AnyField.swift +1 -1
  151. package/ios/Swift/Records/Field.swift +2 -2
  152. package/ios/Swift/Records/Record.swift +5 -5
  153. package/ios/Swift/SharedObjects/SharedObjectRegistry.swift +4 -0
  154. package/ios/Swift/Views/AnyViewProp.swift +1 -1
  155. package/ios/Swift/Views/ComponentData.swift +37 -2
  156. package/ios/Swift/Views/ConcreteViewProp.swift +2 -2
  157. package/ios/Swift/Views/ViewDefinition.swift +39 -0
  158. package/ios/Swift/Views/ViewModuleWrapper.swift +0 -29
  159. package/ios/Tests/ClassComponentSpec.swift +39 -27
  160. package/ios/Tests/ConvertiblesSpec.swift +75 -49
  161. package/ios/Tests/DynamicTypeSpec.swift +29 -27
  162. package/ios/Tests/EitherSpec.swift +9 -7
  163. package/ios/Tests/ExpoModulesSpec.swift +13 -13
  164. package/ios/Tests/FunctionSpec.swift +38 -22
  165. package/ios/Tests/JavaScriptRuntimeSpec.swift +4 -0
  166. package/ios/Tests/PropertyComponentSpec.swift +33 -30
  167. package/ios/Tests/RecordSpec.swift +7 -5
  168. package/ios/Tests/SharedObjectRegistrySpec.swift +12 -12
  169. package/ios/Tests/TypedArraysSpec.swift +1 -1
  170. package/ios/Tests/ViewDefinitionSpec.swift +4 -2
  171. package/package.json +2 -2
  172. package/src/NativeViewManagerAdapter.native.tsx +33 -29
  173. package/src/requireNativeModule.ts +2 -2
  174. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +0 -132
  175. package/ios/EXViewManager.h +0 -21
  176. package/ios/EXViewManager.m +0 -128
  177. package/ios/ModuleRegistryAdapter/EXViewManagerAdapterClassesRegistry.h +0 -17
  178. package/ios/ModuleRegistryAdapter/EXViewManagerAdapterClassesRegistry.m +0 -67
  179. package/ios/ViewManagerAdapter/EXViewManagerAdapter.h +0 -17
  180. package/ios/ViewManagerAdapter/EXViewManagerAdapter.m +0 -45
@@ -7,6 +7,12 @@ import kotlin.reflect.KClass
7
7
  import kotlin.reflect.KProperty1
8
8
  import kotlin.reflect.KType
9
9
 
10
+ @Suppress("NOTHING_TO_INLINE")
11
+ inline fun Throwable.toCodedException() = when (this) {
12
+ is CodedException -> this
13
+ else -> UnexpectedException(this)
14
+ }
15
+
10
16
  /**
11
17
  * A class for errors specifying its `code` and providing the `description`.
12
18
  */
@@ -85,6 +91,7 @@ internal class MissingTypeConverter(
85
91
  message = "Cannot find type converter for '$forType'.",
86
92
  )
87
93
 
94
+ @DoNotStrip
88
95
  internal class InvalidArgsNumberException(received: Int, expected: Int, required: Int = expected) :
89
96
  CodedException(
90
97
  message = if (required < expected) {
@@ -145,6 +152,14 @@ internal class PropSetException(
145
152
  cause,
146
153
  )
147
154
 
155
+ internal class OnViewDidUpdatePropsException(
156
+ viewType: KClass<*>,
157
+ cause: CodedException
158
+ ) : DecoratedException(
159
+ message = "Error occurred when invoking 'onViewDidUpdateProps' on '${viewType.simpleName}'",
160
+ cause
161
+ )
162
+
148
163
  internal class ArgumentCastException(
149
164
  argDesiredType: KType,
150
165
  argIndex: Int,
@@ -165,6 +180,12 @@ internal class ArgumentCastException(
165
180
  }
166
181
  }
167
182
 
183
+ internal class InvalidSharedObjectException(
184
+ sharedType: KType,
185
+ ) : CodedException(
186
+ message = "Cannot convert provided JavaScriptObject to the '$sharedType', because it doesn't contain valid id"
187
+ )
188
+
168
189
  internal class FieldCastException(
169
190
  fieldName: String,
170
191
  fieldType: KType,
@@ -14,6 +14,11 @@ class Exceptions {
14
14
  viewTag: Int
15
15
  ) : CodedException(message = "Unable to find the $viewType view with tag $viewTag")
16
16
 
17
+ /**
18
+ * The app context is no longer available.
19
+ */
20
+ class AppContextLost : CodedException(message = "The app context has been lost")
21
+
17
22
  /**
18
23
  * The react app context is no longer available.
19
24
  */
@@ -43,4 +48,14 @@ class Exceptions {
43
48
  * An exception to throw when the current Android activity is not longer available.
44
49
  */
45
50
  class MissingActivity : CodedException(message = "The current activity is no longer available")
51
+
52
+ /**
53
+ * An exception to throw when function was called on the incorrect thread.
54
+ */
55
+ class IncorrectThreadException(
56
+ currentThreadName: String,
57
+ expectedThreadName: String
58
+ ) : CodedException(
59
+ message = "Expected to run on $expectedThreadName thread, but was run on $currentThreadName"
60
+ )
46
61
  }
@@ -9,8 +9,12 @@ import expo.modules.kotlin.exception.exceptionDecorator
9
9
  import expo.modules.kotlin.iterator
10
10
  import expo.modules.kotlin.jni.ExpectedType
11
11
  import expo.modules.kotlin.jni.JavaScriptModuleObject
12
+ import expo.modules.kotlin.jni.JavaScriptObject
12
13
  import expo.modules.kotlin.recycle
13
14
  import expo.modules.kotlin.types.AnyType
15
+ import kotlin.reflect.KType
16
+ import kotlin.reflect.full.isSubtypeOf
17
+ import kotlin.reflect.typeOf
14
18
 
15
19
  /**
16
20
  * Base class of all exported functions
@@ -21,6 +25,16 @@ abstract class AnyFunction(
21
25
  ) {
22
26
  internal val argsCount get() = desiredArgsTypes.size
23
27
 
28
+ internal var canTakeOwner: Boolean = false
29
+
30
+ @PublishedApi
31
+ internal var ownerType: KType? = null
32
+
33
+ internal val takesOwner: Boolean
34
+ get() = canTakeOwner &&
35
+ desiredArgsTypes.firstOrNull()?.kType?.isSubtypeOf(typeOf<JavaScriptObject>()) == true ||
36
+ (ownerType != null && desiredArgsTypes.firstOrNull()?.kType?.isSubtypeOf(ownerType!!) == true)
37
+
24
38
  /**
25
39
  * A minimum number of arguments the functions needs which equals to `argumentsCount` reduced by the number of trailing optional arguments.
26
40
  */
@@ -29,7 +43,7 @@ abstract class AnyFunction(
29
43
  .reversed()
30
44
  .indexOfFirst { !it.kType.isMarkedNullable }
31
45
  if (nonNullableArgIndex < 0) {
32
- return@run desiredArgsTypes.size
46
+ return@run 0
33
47
  }
34
48
 
35
49
  return@run desiredArgsTypes.size - nonNullableArgIndex
@@ -69,7 +83,7 @@ abstract class AnyFunction(
69
83
  * @throws `CodedException` if conversion isn't possible
70
84
  */
71
85
  @Throws(CodedException::class)
72
- protected fun convertArgs(args: Array<Any?>): Array<out Any?> {
86
+ protected fun convertArgs(args: Array<Any?>, appContext: AppContext? = null): Array<out Any?> {
73
87
  if (requiredArgumentsCount > args.size || args.size > desiredArgsTypes.size) {
74
88
  throw InvalidArgsNumberException(args.size, desiredArgsTypes.size, requiredArgumentsCount)
75
89
  }
@@ -82,7 +96,7 @@ abstract class AnyFunction(
82
96
  exceptionDecorator({ cause ->
83
97
  ArgumentCastException(desiredType.kType, index, element?.javaClass.toString(), cause)
84
98
  }) {
85
- finalArgs[index] = desiredType.convert(element)
99
+ finalArgs[index] = desiredType.convert(element, appContext)
86
100
  }
87
101
  }
88
102
  return finalArgs
@@ -1,10 +1,16 @@
1
1
  package expo.modules.kotlin.functions
2
2
 
3
+ import com.facebook.react.bridge.ReactContext
3
4
  import com.facebook.react.bridge.ReadableArray
5
+ import com.facebook.react.uimanager.UIManagerHelper
6
+ import com.facebook.react.uimanager.UIManagerModule
7
+ import com.facebook.react.uimanager.common.UIManagerType
8
+ import expo.modules.BuildConfig
4
9
  import expo.modules.kotlin.AppContext
5
10
  import expo.modules.kotlin.ModuleHolder
6
11
  import expo.modules.kotlin.Promise
7
12
  import expo.modules.kotlin.exception.CodedException
13
+ import expo.modules.kotlin.exception.Exceptions
8
14
  import expo.modules.kotlin.exception.FunctionCallException
9
15
  import expo.modules.kotlin.exception.UnexpectedException
10
16
  import expo.modules.kotlin.exception.exceptionDecorator
@@ -48,25 +54,21 @@ abstract class AsyncFunction(
48
54
  @Throws(CodedException::class)
49
55
  internal abstract fun callUserImplementation(args: ReadableArray, promise: Promise)
50
56
 
51
- internal abstract fun callUserImplementation(args: Array<Any?>, promise: Promise)
57
+ internal abstract fun callUserImplementation(args: Array<Any?>, promise: Promise, appContext: AppContext)
52
58
 
53
59
  override fun attachToJSObject(appContext: AppContext, jsObject: JavaScriptModuleObject) {
54
60
  jsObject.registerAsyncFunction(
55
61
  name,
62
+ takesOwner,
56
63
  argsCount,
57
64
  desiredArgsTypes.map { it.getCppRequiredTypes() }.toTypedArray()
58
65
  ) { args, bridgePromise ->
59
- val queue = when (queue) {
60
- Queues.MAIN -> appContext.mainQueue
61
- Queues.DEFAULT -> appContext.modulesQueue
62
- }
63
-
64
- queue.launch {
66
+ val functionBody = {
65
67
  try {
66
68
  exceptionDecorator({
67
69
  FunctionCallException(name, jsObject.name, it)
68
70
  }) {
69
- callUserImplementation(args, bridgePromise)
71
+ callUserImplementation(args, bridgePromise, appContext)
70
72
  }
71
73
  } catch (e: CodedException) {
72
74
  bridgePromise.reject(e)
@@ -74,6 +76,34 @@ abstract class AsyncFunction(
74
76
  bridgePromise.reject(UnexpectedException(e))
75
77
  }
76
78
  }
79
+
80
+ if (queue == Queues.MAIN) {
81
+ if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
82
+ // On certain occasions, invoking a function on a view could lead to an error
83
+ // because of the asynchronous communication between the JavaScript and native components.
84
+ // In such cases, the native view may not have been mounted yet,
85
+ // but the JavaScript code has already received the future tag of the view.
86
+ // To avoid this issue, we have decided to temporarily utilize
87
+ // the UIManagerModule for dispatching functions on the main thread.
88
+ val uiManager = UIManagerHelper.getUIManagerForReactTag(
89
+ appContext.reactContext as? ReactContext ?: throw Exceptions.ReactContextLost(),
90
+ UIManagerType.DEFAULT
91
+ ) as UIManagerModule
92
+
93
+ uiManager.addUIBlock {
94
+ functionBody()
95
+ }
96
+ return@registerAsyncFunction
97
+ }
98
+
99
+ appContext.mainQueue.launch {
100
+ functionBody()
101
+ }
102
+ } else {
103
+ appContext.modulesQueue.launch {
104
+ functionBody()
105
+ }
106
+ }
77
107
  }
78
108
  }
79
109
  }
@@ -1,6 +1,7 @@
1
1
  package expo.modules.kotlin.functions
2
2
 
3
3
  import com.facebook.react.bridge.ReadableArray
4
+ import expo.modules.kotlin.AppContext
4
5
  import expo.modules.kotlin.Promise
5
6
  import expo.modules.kotlin.exception.CodedException
6
7
  import expo.modules.kotlin.types.AnyType
@@ -15,7 +16,7 @@ class AsyncFunctionComponent(
15
16
  promise.resolve(body(convertArgs(args)))
16
17
  }
17
18
 
18
- override fun callUserImplementation(args: Array<Any?>, promise: Promise) {
19
- promise.resolve(body(convertArgs(args)))
19
+ override fun callUserImplementation(args: Array<Any?>, promise: Promise, appContext: AppContext) {
20
+ promise.resolve(body(convertArgs(args, appContext)))
20
21
  }
21
22
  }
@@ -1,6 +1,7 @@
1
1
  package expo.modules.kotlin.functions
2
2
 
3
3
  import com.facebook.react.bridge.ReadableArray
4
+ import expo.modules.kotlin.AppContext
4
5
  import expo.modules.kotlin.Promise
5
6
  import expo.modules.kotlin.exception.CodedException
6
7
  import expo.modules.kotlin.types.AnyType
@@ -15,7 +16,7 @@ class AsyncFunctionWithPromiseComponent(
15
16
  body(convertArgs(args), promise)
16
17
  }
17
18
 
18
- override fun callUserImplementation(args: Array<Any?>, promise: Promise) {
19
- body(convertArgs(args), promise)
19
+ override fun callUserImplementation(args: Array<Any?>, promise: Promise, appContext: AppContext) {
20
+ body(convertArgs(args, appContext), promise)
20
21
  }
21
22
  }
@@ -48,6 +48,7 @@ class SuspendFunctionComponent(
48
48
  override fun attachToJSObject(appContext: AppContext, jsObject: JavaScriptModuleObject) {
49
49
  jsObject.registerAsyncFunction(
50
50
  name,
51
+ takesOwner,
51
52
  argsCount,
52
53
  desiredArgsTypes.map { it.getCppRequiredTypes() }.toTypedArray()
53
54
  ) { args, bridgePromise ->
@@ -5,6 +5,7 @@ import expo.modules.kotlin.AppContext
5
5
  import expo.modules.kotlin.exception.CodedException
6
6
  import expo.modules.kotlin.exception.FunctionCallException
7
7
  import expo.modules.kotlin.exception.exceptionDecorator
8
+ import expo.modules.kotlin.jni.JNIFunctionBody
8
9
  import expo.modules.kotlin.jni.JavaScriptModuleObject
9
10
  import expo.modules.kotlin.types.AnyType
10
11
  import expo.modules.kotlin.types.JSTypeConverter
@@ -19,22 +20,28 @@ class SyncFunctionComponent(
19
20
  return body(convertArgs(args))
20
21
  }
21
22
 
22
- fun call(args: Array<Any?>): Any? {
23
- return body(convertArgs(args))
23
+ fun call(args: Array<Any?>, appContext: AppContext? = null): Any? {
24
+ return body(convertArgs(args, appContext))
24
25
  }
25
26
 
26
- override fun attachToJSObject(appContext: AppContext, jsObject: JavaScriptModuleObject) {
27
- jsObject.registerSyncFunction(
28
- name,
29
- argsCount,
30
- getCppRequiredTypes().toTypedArray()
31
- ) { args ->
32
- return@registerSyncFunction exceptionDecorator({
33
- FunctionCallException(name, jsObject.name, it)
27
+ internal fun getJNIFunctionBody(moduleName: String, appContext: AppContext?): JNIFunctionBody {
28
+ return JNIFunctionBody { args ->
29
+ return@JNIFunctionBody exceptionDecorator({
30
+ FunctionCallException(name, moduleName, it)
34
31
  }) {
35
- val result = call(args)
32
+ val result = call(args, appContext)
36
33
  return@exceptionDecorator JSTypeConverter.convertToJSValue(result)
37
34
  }
38
35
  }
39
36
  }
37
+
38
+ override fun attachToJSObject(appContext: AppContext, jsObject: JavaScriptModuleObject) {
39
+ jsObject.registerSyncFunction(
40
+ name,
41
+ takesOwner,
42
+ argsCount,
43
+ getCppRequiredTypes().toTypedArray(),
44
+ getJNIFunctionBody(jsObject.name, appContext)
45
+ )
46
+ }
40
47
  }
@@ -27,5 +27,8 @@ enum class CppType(val clazz: KClass<*>, val value: Int = nextValue()) {
27
27
  TYPED_ARRAY(TypedArray::class),
28
28
  PRIMITIVE_ARRAY(Array::class),
29
29
  LIST(List::class),
30
- MAP(Map::class);
30
+ MAP(Map::class),
31
+ VIEW_TAG(Int::class),
32
+ SHARED_OBJECT_ID(Int::class),
33
+ JS_FUNCTION(JavaScriptFunction::class);
31
34
  }
@@ -0,0 +1,73 @@
1
+ package expo.modules.kotlin.jni
2
+
3
+ import java.lang.ref.PhantomReference
4
+ import java.lang.ref.ReferenceQueue
5
+ import java.lang.ref.WeakReference
6
+
7
+ interface Destructible {
8
+ fun deallocate()
9
+ }
10
+
11
+ object JNIDeallocator {
12
+ /**
13
+ * A [PhantomReference] queue managed by JVM
14
+ */
15
+ private val referenceQueue = ReferenceQueue<Destructible>()
16
+
17
+ /**
18
+ * A registry to keep all active [Destructible] objects and their [PhantomReference]s
19
+ */
20
+ private val destructorMap = mutableMapOf<PhantomReference<Destructible>, WeakReference<Destructible>>()
21
+
22
+ /**
23
+ * A thread that clears your registry when an object has been garbage collected
24
+ * to not store invalid references to every created object.
25
+ */
26
+ private val destructorThread = object : Thread("Expo JNI deallocator") {
27
+ override fun run() {
28
+ while (true) {
29
+ try {
30
+ // Referent of PhantomReference were garbage collected so we can remove it from our registry.
31
+ // Note that we don't have to call `deallocate` method - it was called [com.facebook.jni.HybridData].
32
+ val current = referenceQueue.remove()
33
+ synchronized(this) {
34
+ destructorMap.remove(current)
35
+ }
36
+ } catch (e: InterruptedException) {
37
+ // Continue. This thread should never be terminated.
38
+ }
39
+ }
40
+ }
41
+ }.also {
42
+ it.start()
43
+ }
44
+
45
+ /**
46
+ * Adds reference to the internal registry.
47
+ * That reference will be deallocated when [JNIDeallocator.deallocate] is called or
48
+ * when the reference won't be reachable by the GC.
49
+ */
50
+ internal fun addReference(destructible: Destructible) = synchronized(this) {
51
+ val weakRef = WeakReference(destructible)
52
+ val phantomRef = PhantomReference(destructible, referenceQueue)
53
+ destructorMap[phantomRef] = weakRef
54
+ }
55
+
56
+ /**
57
+ * Deallocates valid references and clears the internal registry.
58
+ */
59
+ internal fun deallocate() = synchronized(this) {
60
+ destructorMap.values.forEach {
61
+ it.get()?.deallocate()
62
+ }
63
+ destructorMap.clear()
64
+ }
65
+
66
+ /**
67
+ * Returns references to all hybrid objects that contain references to the jsi value
68
+ * and are present in the memory.
69
+ */
70
+ fun inspectMemory() = synchronized(this) {
71
+ destructorMap.values.mapNotNull { it.get() }
72
+ }
73
+ }
@@ -6,12 +6,19 @@ import com.facebook.soloader.SoLoader
6
6
  import expo.modules.core.interfaces.DoNotStrip
7
7
  import expo.modules.kotlin.AppContext
8
8
  import expo.modules.kotlin.exception.JavaScriptEvaluateException
9
+ import expo.modules.kotlin.sharedobjects.SharedObject
9
10
  import java.lang.ref.WeakReference
10
11
 
12
+ /**
13
+ * Despite the fact that this class is marked as [Destructible], it is not included in the [JNIDeallocator].
14
+ * The deallocation of the [JSIInteropModuleRegistry] should be performed at the very end
15
+ * to prevent the destructor of the [Destructible] object from accessing data that has already been freed.
16
+ */
11
17
  @Suppress("KotlinJniMissingFunction")
12
18
  @DoNotStrip
13
- class JSIInteropModuleRegistry(appContext: AppContext) {
14
- private val appContextHolder = WeakReference(appContext)
19
+ class JSIInteropModuleRegistry(appContext: AppContext) : Destructible {
20
+
21
+ internal val appContextHolder = WeakReference(appContext)
15
22
 
16
23
  // Has to be called "mHybridData" - fbjni uses it via reflection
17
24
  @DoNotStrip
@@ -68,6 +75,12 @@ class JSIInteropModuleRegistry(appContext: AppContext) {
68
75
  return appContextHolder.get()?.registry?.getModuleHolder(name)?.jsObject
69
76
  }
70
77
 
78
+ @Suppress("unused")
79
+ @DoNotStrip
80
+ fun hasModule(name: String): Boolean {
81
+ return appContextHolder.get()?.registry?.hasModule(name) ?: false
82
+ }
83
+
71
84
  /**
72
85
  * Returns an array that contains names of available modules.
73
86
  */
@@ -77,8 +90,21 @@ class JSIInteropModuleRegistry(appContext: AppContext) {
77
90
  return appContextHolder.get()?.registry?.registry?.keys?.toTypedArray() ?: emptyArray()
78
91
  }
79
92
 
93
+ @Suppress("unused")
94
+ @DoNotStrip
95
+ fun registerSharedObject(native: Any, js: JavaScriptObject) {
96
+ appContextHolder
97
+ .get()
98
+ ?.sharedObjectRegistry
99
+ ?.add(native as SharedObject, js)
100
+ }
101
+
80
102
  @Throws(Throwable::class)
81
103
  protected fun finalize() {
104
+ deallocate()
105
+ }
106
+
107
+ override fun deallocate() {
82
108
  mHybridData.resetNative()
83
109
  }
84
110
 
@@ -8,7 +8,10 @@ import expo.modules.kotlin.exception.UnexpectedException
8
8
 
9
9
  @Suppress("KotlinJniMissingFunction")
10
10
  @DoNotStrip
11
- class JavaCallback @DoNotStrip internal constructor(@DoNotStrip private val mHybridData: HybridData) {
11
+ class JavaCallback @DoNotStrip internal constructor(@DoNotStrip private val mHybridData: HybridData) : Destructible {
12
+ init {
13
+ JNIDeallocator.addReference(this)
14
+ }
12
15
 
13
16
  operator fun invoke(result: Any?) {
14
17
  if (result == null) {
@@ -38,6 +41,10 @@ class JavaCallback @DoNotStrip internal constructor(@DoNotStrip private val mHyb
38
41
 
39
42
  @Throws(Throwable::class)
40
43
  protected fun finalize() {
44
+ deallocate()
45
+ }
46
+
47
+ override fun deallocate() {
41
48
  mHybridData.resetNative()
42
49
  }
43
50
  }
@@ -0,0 +1,48 @@
1
+ package expo.modules.kotlin.jni
2
+
3
+ import com.facebook.jni.HybridData
4
+ import expo.modules.core.interfaces.DoNotStrip
5
+ import expo.modules.kotlin.AppContext
6
+ import expo.modules.kotlin.types.JSTypeConverter
7
+ import expo.modules.kotlin.types.TypeConverterProviderImpl
8
+ import kotlin.reflect.KType
9
+ import kotlin.reflect.typeOf
10
+
11
+ @Suppress("KotlinJniMissingFunction")
12
+ @DoNotStrip
13
+ class JavaScriptFunction<ReturnType : Any?> @DoNotStrip private constructor(@DoNotStrip private val mHybridData: HybridData) : Destructible {
14
+ init {
15
+ JNIDeallocator.addReference(this)
16
+ }
17
+
18
+ @PublishedApi
19
+ internal var returnType: KType? = null
20
+
21
+ fun isValid() = mHybridData.isValid
22
+
23
+ private external fun invoke(args: Array<Any?>, expectedReturnType: ExpectedType): Any?
24
+
25
+ operator fun invoke(vararg args: Any?, appContext: AppContext? = null): ReturnType {
26
+ // TODO(@lukmccall): check current thread
27
+ val convertedArgs = args
28
+ .map { JSTypeConverter.convertToJSValue(it) }
29
+ .toTypedArray()
30
+
31
+ val converter = TypeConverterProviderImpl
32
+ .obtainTypeConverter(returnType ?: typeOf<Unit>())
33
+
34
+ val expectedReturnType = converter.getCppRequiredTypes()
35
+ val result = invoke(convertedArgs, expectedReturnType)
36
+ @Suppress("UNCHECKED_CAST")
37
+ return converter.convert(result, appContext) as ReturnType
38
+ }
39
+
40
+ @Throws(Throwable::class)
41
+ protected fun finalize() {
42
+ deallocate()
43
+ }
44
+
45
+ override fun deallocate() {
46
+ mHybridData.resetNative()
47
+ }
48
+ }
@@ -1,8 +1,11 @@
1
1
  package expo.modules.kotlin.jni
2
2
 
3
3
  import com.facebook.jni.HybridData
4
+ import com.facebook.react.bridge.Arguments
4
5
  import com.facebook.react.bridge.NativeMap
5
6
  import expo.modules.core.interfaces.DoNotStrip
7
+ import expo.modules.kotlin.AppContext
8
+ import expo.modules.kotlin.objects.ObjectDefinitionData
6
9
 
7
10
  /**
8
11
  * A class to communicate with CPP part of the [expo.modules.kotlin.modules.Module] class.
@@ -13,13 +16,35 @@ import expo.modules.core.interfaces.DoNotStrip
13
16
  */
14
17
  @Suppress("KotlinJniMissingFunction")
15
18
  @DoNotStrip
16
- class JavaScriptModuleObject(val name: String) {
19
+ class JavaScriptModuleObject(val name: String) : Destructible {
17
20
  // Has to be called "mHybridData" - fbjni uses it via reflection
18
21
  @DoNotStrip
19
22
  private val mHybridData = initHybrid()
20
23
 
21
24
  private external fun initHybrid(): HybridData
22
25
 
26
+ init {
27
+ JNIDeallocator.addReference(this)
28
+ }
29
+
30
+ fun initUsingObjectDefinition(appContext: AppContext, definition: ObjectDefinitionData) = apply {
31
+ val constants = definition.constantsProvider()
32
+ val convertedConstants = Arguments.makeNativeMap(constants)
33
+ exportConstants(convertedConstants)
34
+
35
+ definition
36
+ .functions
37
+ .forEach { function ->
38
+ function.attachToJSObject(appContext, this)
39
+ }
40
+
41
+ definition
42
+ .properties
43
+ .forEach { (_, prop) ->
44
+ prop.attachToJSObject(this)
45
+ }
46
+ }
47
+
23
48
  /**
24
49
  * Exports constants
25
50
  */
@@ -29,18 +54,30 @@ class JavaScriptModuleObject(val name: String) {
29
54
  * Register a promise-less function on the CPP module representation.
30
55
  * After calling this function, user can access the exported function in the JS code.
31
56
  */
32
- external fun registerSyncFunction(name: String, args: Int, desiredTypes: Array<ExpectedType>, body: JNIFunctionBody)
57
+ external fun registerSyncFunction(name: String, takesOwner: Boolean, args: Int, desiredTypes: Array<ExpectedType>, body: JNIFunctionBody)
33
58
 
34
59
  /**
35
60
  * Register a promise function on the CPP module representation.
36
61
  * After calling this function, user can access the exported function in the JS code.
37
62
  */
38
- external fun registerAsyncFunction(name: String, args: Int, desiredTypes: Array<ExpectedType>, body: JNIAsyncFunctionBody)
63
+ external fun registerAsyncFunction(name: String, takesOwner: Boolean, args: Int, desiredTypes: Array<ExpectedType>, body: JNIAsyncFunctionBody)
39
64
 
40
65
  external fun registerProperty(name: String, desiredType: ExpectedType, getter: JNIFunctionBody?, setter: JNIFunctionBody?)
41
66
 
67
+ external fun registerClass(name: String, classModule: JavaScriptModuleObject, takesOwner: Boolean, args: Int, desiredTypes: Array<ExpectedType>, body: JNIFunctionBody)
68
+
69
+ external fun registerViewPrototype(viewPrototype: JavaScriptModuleObject)
70
+
42
71
  @Throws(Throwable::class)
43
72
  protected fun finalize() {
73
+ deallocate()
74
+ }
75
+
76
+ override fun deallocate() {
44
77
  mHybridData.resetNative()
45
78
  }
79
+
80
+ override fun toString(): String {
81
+ return "JavaScriptModuleObject_$name"
82
+ }
46
83
  }
@@ -9,7 +9,12 @@ import expo.modules.core.interfaces.DoNotStrip
9
9
  */
10
10
  @Suppress("KotlinJniMissingFunction")
11
11
  @DoNotStrip
12
- open class JavaScriptObject @DoNotStrip internal constructor(@DoNotStrip private val mHybridData: HybridData) {
12
+ open class JavaScriptObject @DoNotStrip internal constructor(@DoNotStrip private val mHybridData: HybridData) : Destructible {
13
+ init {
14
+ @Suppress("LeakingThis")
15
+ JNIDeallocator.addReference(this)
16
+ }
17
+
13
18
  /**
14
19
  * The property descriptor options for the property being defined or modified.
15
20
  */
@@ -30,23 +35,34 @@ open class JavaScriptObject @DoNotStrip internal constructor(@DoNotStrip private
30
35
  Writable(1 shl 2),
31
36
  }
32
37
 
38
+ fun isValid() = mHybridData.isValid
39
+
33
40
  external fun hasProperty(name: String): Boolean
34
41
  external fun getProperty(name: String): JavaScriptValue
35
- external fun getPropertyNames(): Array<String>
36
42
 
43
+ external fun getPropertyNames(): Array<String>
37
44
  private external fun setBoolProperty(name: String, value: Boolean)
38
45
  private external fun setDoubleProperty(name: String, value: Double)
39
46
  private external fun setStringProperty(name: String, value: String?)
40
47
  private external fun setJSValueProperty(name: String, value: JavaScriptValue?)
41
48
  private external fun setJSObjectProperty(name: String, value: JavaScriptObject?)
42
- private external fun unsetProperty(name: String)
43
49
 
50
+ private external fun unsetProperty(name: String)
44
51
  private external fun defineBoolProperty(name: String, value: Boolean, options: Int)
45
52
  private external fun defineDoubleProperty(name: String, value: Double, options: Int)
46
53
  private external fun defineStringProperty(name: String, value: String?, options: Int)
47
54
  private external fun defineJSValueProperty(name: String, value: JavaScriptValue?, options: Int)
55
+
48
56
  private external fun defineJSObjectProperty(name: String, value: JavaScriptObject?, options: Int)
49
57
 
58
+ private external fun defineNativeDeallocator(deallocator: JNIFunctionBody)
59
+
60
+ internal fun defineDeallocator(deallocator: () -> Unit) {
61
+ defineNativeDeallocator {
62
+ deallocator()
63
+ }
64
+ }
65
+
50
66
  fun setProperty(name: String, value: Boolean) = setBoolProperty(name, value)
51
67
  fun setProperty(name: String, value: Int) = setDoubleProperty(name, value.toDouble())
52
68
  fun setProperty(name: String, value: Double) = setDoubleProperty(name, value)
@@ -103,6 +119,10 @@ open class JavaScriptObject @DoNotStrip internal constructor(@DoNotStrip private
103
119
 
104
120
  @Throws(Throwable::class)
105
121
  protected fun finalize() {
122
+ deallocate()
123
+ }
124
+
125
+ override fun deallocate() {
106
126
  mHybridData.resetNative()
107
127
  }
108
128
  }