expo-modules-core 0.6.1 → 0.7.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 (161) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/android/build.gradle +5 -5
  4. package/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt +7 -1
  5. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityLifecycleListener.java +23 -0
  6. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +11 -1
  7. package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +5 -3
  8. package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +8 -2
  9. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +13 -4
  10. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +7 -6
  11. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +6 -1
  12. package/android/src/main/java/expo/modules/kotlin/Promise.kt +1 -1
  13. package/android/src/main/java/expo/modules/kotlin/callbacks/Callback.kt +5 -0
  14. package/android/src/main/java/expo/modules/kotlin/callbacks/ViewCallback.kt +28 -0
  15. package/android/src/main/java/expo/modules/kotlin/callbacks/ViewCallbackDelegate.kt +27 -0
  16. package/android/src/main/java/expo/modules/kotlin/defaultmodules/ErrorManagerModule.kt +25 -0
  17. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +68 -8
  18. package/android/src/main/java/expo/modules/kotlin/exception/ExceptionDecorator.kt +11 -0
  19. package/android/src/main/java/expo/modules/kotlin/methods/AnyMethod.kt +13 -14
  20. package/android/src/main/java/expo/modules/kotlin/modules/DefinitionMarker.kt +4 -0
  21. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +3 -2
  22. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +44 -4
  23. package/android/src/main/java/expo/modules/kotlin/records/Record.kt +39 -0
  24. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +14 -7
  25. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +11 -5
  26. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +10 -4
  27. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +12 -6
  28. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +29 -13
  29. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +2 -1
  30. package/android/src/main/java/expo/modules/kotlin/views/CallbacksDefinition.kt +3 -0
  31. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +22 -0
  32. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +22 -0
  33. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +27 -2
  34. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +29 -1
  35. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +59 -2
  36. package/build/EventEmitter.d.ts +1 -0
  37. package/build/EventEmitter.d.ts.map +1 -0
  38. package/build/NativeModulesProxy.d.ts +1 -0
  39. package/build/NativeModulesProxy.d.ts.map +1 -0
  40. package/build/NativeModulesProxy.native.d.ts +1 -0
  41. package/build/NativeModulesProxy.native.d.ts.map +1 -0
  42. package/build/NativeModulesProxy.types.d.ts +1 -0
  43. package/build/NativeModulesProxy.types.d.ts.map +1 -0
  44. package/build/NativeViewManagerAdapter.d.ts +1 -0
  45. package/build/NativeViewManagerAdapter.d.ts.map +1 -0
  46. package/build/NativeViewManagerAdapter.native.d.ts +1 -0
  47. package/build/NativeViewManagerAdapter.native.d.ts.map +1 -0
  48. package/build/PermissionsHook.d.ts +1 -0
  49. package/build/PermissionsHook.d.ts.map +1 -0
  50. package/build/PermissionsInterface.d.ts +1 -0
  51. package/build/PermissionsInterface.d.ts.map +1 -0
  52. package/build/Platform.d.ts +1 -0
  53. package/build/Platform.d.ts.map +1 -0
  54. package/build/SyntheticPlatformEmitter.d.ts +1 -0
  55. package/build/SyntheticPlatformEmitter.d.ts.map +1 -0
  56. package/build/SyntheticPlatformEmitter.web.d.ts +1 -0
  57. package/build/SyntheticPlatformEmitter.web.d.ts.map +1 -0
  58. package/build/deprecate.d.ts +1 -0
  59. package/build/deprecate.d.ts.map +1 -0
  60. package/build/environment/browser.d.ts +1 -0
  61. package/build/environment/browser.d.ts.map +1 -0
  62. package/build/environment/browser.web.d.ts +1 -0
  63. package/build/environment/browser.web.d.ts.map +1 -0
  64. package/build/errors/CodedError.d.ts +1 -0
  65. package/build/errors/CodedError.d.ts.map +1 -0
  66. package/build/errors/UnavailabilityError.d.ts +1 -0
  67. package/build/errors/UnavailabilityError.d.ts.map +1 -0
  68. package/build/index.d.ts +3 -0
  69. package/build/index.d.ts.map +1 -0
  70. package/build/index.js +2 -0
  71. package/build/index.js.map +1 -1
  72. package/build/requireNativeModule.d.ts +16 -0
  73. package/build/requireNativeModule.d.ts.map +1 -0
  74. package/build/requireNativeModule.js +18 -0
  75. package/build/requireNativeModule.js.map +1 -0
  76. package/build/sweet/NativeErrorManager.d.ts +3 -0
  77. package/build/sweet/NativeErrorManager.d.ts.map +1 -0
  78. package/build/sweet/NativeErrorManager.js +3 -0
  79. package/build/sweet/NativeErrorManager.js.map +1 -0
  80. package/build/sweet/setUpErrorManager.fx.d.ts +2 -0
  81. package/build/sweet/setUpErrorManager.fx.d.ts.map +1 -0
  82. package/build/sweet/setUpErrorManager.fx.js +11 -0
  83. package/build/sweet/setUpErrorManager.fx.js.map +1 -0
  84. package/ios/AppDelegates/ExpoAppDelegate.swift +27 -8
  85. package/ios/JSI/ExpoModulesHostObject.h +33 -0
  86. package/ios/JSI/ExpoModulesHostObject.mm +40 -0
  87. package/ios/JSI/ExpoModulesProxySpec.h +4 -0
  88. package/ios/JSI/ExpoModulesProxySpec.mm +1 -3
  89. package/ios/JSI/JSIConversions.h +2 -0
  90. package/ios/JSI/JSIConversions.mm +9 -0
  91. package/ios/JSI/JSIInstaller.h +10 -0
  92. package/ios/JSI/JSIInstaller.mm +14 -2
  93. package/ios/JSI/JavaScriptObject.h +60 -0
  94. package/ios/JSI/JavaScriptObject.mm +93 -0
  95. package/ios/JSI/JavaScriptRuntime.h +54 -0
  96. package/ios/JSI/JavaScriptRuntime.mm +102 -0
  97. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +2 -12
  98. package/ios/NativeModulesProxy/EXComponentDataCompatibleWrapper.h +16 -0
  99. package/ios/NativeModulesProxy/EXComponentDataCompatibleWrapper.m +28 -0
  100. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +90 -66
  101. package/ios/RCTComponentData+Privates.h +12 -0
  102. package/ios/ReactDelegates/ExpoReactDelegate.swift +2 -2
  103. package/ios/ReactDelegates/ExpoReactDelegateHandler.swift +3 -3
  104. package/ios/ReactDelegates/ModulePriorities.swift +1 -1
  105. package/ios/Swift/AppContext.swift +38 -4
  106. package/ios/Swift/Arguments/ArgumentType.swift +4 -0
  107. package/ios/Swift/Arguments/Convertibles.swift +13 -13
  108. package/ios/Swift/Arguments/Types/EnumArgumentType.swift +11 -17
  109. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +1 -1
  110. package/ios/Swift/Arguments/Types/RawArgumentType.swift +2 -2
  111. package/ios/Swift/Conversions.swift +51 -56
  112. package/ios/Swift/EventListener.swift +8 -10
  113. package/ios/Swift/Events/Callback.swift +66 -0
  114. package/ios/Swift/Events/Event.swift +43 -0
  115. package/ios/Swift/Exceptions/ChainableException.swift +51 -0
  116. package/ios/Swift/{CodedError.swift → Exceptions/CodedError.swift} +1 -12
  117. package/ios/Swift/Exceptions/Exception.swift +62 -0
  118. package/ios/Swift/Exceptions/ExceptionOrigin.swift +28 -0
  119. package/ios/Swift/Exceptions/GenericException.swift +20 -0
  120. package/ios/Swift/Exceptions/UnexpectedException.swift +16 -0
  121. package/ios/Swift/Functions/AnyFunction.swift +11 -1
  122. package/ios/Swift/Functions/ConcreteFunction.swift +37 -16
  123. package/ios/Swift/JavaScriptUtils.swift +43 -0
  124. package/ios/Swift/ModuleHolder.swift +53 -14
  125. package/ios/Swift/ModuleRegistry.swift +4 -1
  126. package/ios/Swift/Modules/AnyModule.swift +0 -1
  127. package/ios/Swift/Modules/ModuleDefinition.swift +4 -13
  128. package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +0 -1
  129. package/ios/Swift/Modules/ModuleDefinitionComponents.swift +0 -188
  130. package/ios/Swift/ModulesProvider.swift +0 -1
  131. package/ios/Swift/Objects/ObjectDefinition.swift +30 -0
  132. package/ios/Swift/Objects/ObjectDefinitionComponents.swift +208 -0
  133. package/ios/Swift/Promise.swift +8 -3
  134. package/ios/Swift/Records/AnyField.swift +7 -0
  135. package/ios/Swift/Records/Field.swift +24 -19
  136. package/ios/Swift/Records/FieldOption.swift +1 -1
  137. package/ios/Swift/Records/Record.swift +12 -4
  138. package/ios/Swift/SwiftInteropBridge.swift +39 -10
  139. package/ios/Swift/Views/AnyViewProp.swift +1 -1
  140. package/ios/Swift/Views/ComponentData.swift +95 -0
  141. package/ios/Swift/Views/ConcreteViewProp.swift +6 -8
  142. package/ios/Swift/Views/ViewFactory.swift +1 -1
  143. package/ios/Swift/Views/ViewManagerDefinition.swift +23 -2
  144. package/ios/Swift/Views/ViewManagerDefinitionBuilder.swift +0 -1
  145. package/ios/Swift/Views/ViewManagerDefinitionComponents.swift +26 -0
  146. package/ios/Swift/Views/ViewModuleWrapper.swift +5 -2
  147. package/ios/Tests/ArgumentTypeSpec.swift +3 -4
  148. package/ios/Tests/ConstantsSpec.swift +4 -4
  149. package/ios/Tests/ConvertiblesSpec.swift +33 -33
  150. package/ios/Tests/ExceptionsSpec.swift +112 -0
  151. package/ios/Tests/FunctionSpec.swift +20 -22
  152. package/ios/Tests/FunctionWithConvertiblesSpec.swift +2 -2
  153. package/ios/Tests/Mocks/ModuleMocks.swift +1 -1
  154. package/ios/Tests/Mocks/ModulesProviderMock.swift +0 -1
  155. package/ios/Tests/ModuleEventListenersSpec.swift +1 -1
  156. package/ios/Tests/RecordSpec.swift +7 -17
  157. package/package.json +3 -3
  158. package/src/index.ts +4 -0
  159. package/src/requireNativeModule.ts +29 -0
  160. package/src/sweet/NativeErrorManager.ts +2 -0
  161. package/src/sweet/setUpErrorManager.fx.ts +12 -0
@@ -31,7 +31,8 @@ import expo.modules.kotlin.views.ViewManagerDefinition
31
31
  import expo.modules.kotlin.views.ViewManagerDefinitionBuilder
32
32
  import kotlin.reflect.typeOf
33
33
 
34
- class ModuleDefinitionBuilder {
34
+ @DefinitionMarker
35
+ class ModuleDefinitionBuilder(private val module: Module? = null) {
35
36
  private var name: String? = null
36
37
  private var constantsProvider = { emptyMap<String, Any?>() }
37
38
  private var eventsDefinition: EventsDefinition? = null
@@ -46,8 +47,10 @@ class ModuleDefinitionBuilder {
46
47
  internal val eventListeners = mutableMapOf<EventName, EventListener>()
47
48
 
48
49
  fun build(): ModuleDefinitionData {
50
+ val moduleName = name ?: module?.javaClass?.simpleName
51
+
49
52
  return ModuleDefinitionData(
50
- requireNotNull(name),
53
+ requireNotNull(moduleName),
51
54
  constantsProvider,
52
55
  methods,
53
56
  viewManagerDefinition,
@@ -56,14 +59,27 @@ class ModuleDefinitionBuilder {
56
59
  )
57
60
  }
58
61
 
62
+ /**
63
+ * Sets the name of the module that is exported to the JavaScript world.
64
+ */
59
65
  fun name(name: String) {
60
66
  this.name = name
61
67
  }
62
68
 
69
+ /**
70
+ * Definition function setting the module's constants to export.
71
+ */
63
72
  fun constants(constantsProvider: () -> Map<String, Any?>) {
64
73
  this.constantsProvider = constantsProvider
65
74
  }
66
75
 
76
+ /**
77
+ * Definition of the module's constants to export.
78
+ */
79
+ fun constants(vararg constants: Pair<String, Any?>) {
80
+ constantsProvider = { constants.toMap() }
81
+ }
82
+
67
83
  @JvmName("methodWithoutArgs")
68
84
  inline fun function(
69
85
  name: String,
@@ -167,6 +183,9 @@ class ModuleDefinitionBuilder {
167
183
  }
168
184
  }
169
185
 
186
+ /**
187
+ * Creates the view manager definition that scopes other view-related definitions.
188
+ */
170
189
  inline fun viewManager(body: ViewManagerDefinitionBuilder.() -> Unit) {
171
190
  require(viewManagerDefinition == null) { "The module definition may have exported only one view manager." }
172
191
 
@@ -175,22 +194,37 @@ class ModuleDefinitionBuilder {
175
194
  viewManagerDefinition = viewManagerDefinitionBuilder.build()
176
195
  }
177
196
 
197
+ /**
198
+ * Creates module's lifecycle listener that is called right after the module initialization.
199
+ */
178
200
  inline fun onCreate(crossinline body: () -> Unit) {
179
201
  eventListeners[EventName.MODULE_CREATE] = BasicEventListener(EventName.MODULE_CREATE) { body() }
180
202
  }
181
203
 
204
+ /**
205
+ * Creates module's lifecycle listener that is called when the module is about to be deallocated.
206
+ */
182
207
  inline fun onDestroy(crossinline body: () -> Unit) {
183
208
  eventListeners[EventName.MODULE_DESTROY] = BasicEventListener(EventName.MODULE_DESTROY) { body() }
184
209
  }
185
210
 
211
+ /**
212
+ * Creates module's lifecycle listener that is called right after the activity is resumed.
213
+ */
186
214
  inline fun onActivityEntersForeground(crossinline body: () -> Unit) {
187
215
  eventListeners[EventName.ACTIVITY_ENTERS_FOREGROUND] = BasicEventListener(EventName.ACTIVITY_ENTERS_FOREGROUND) { body() }
188
216
  }
189
217
 
218
+ /**
219
+ * Creates module's lifecycle listener that is called right after the activity is paused.
220
+ */
190
221
  inline fun onActivityEntersBackground(crossinline body: () -> Unit) {
191
222
  eventListeners[EventName.ACTIVITY_ENTERS_BACKGROUND] = BasicEventListener(EventName.ACTIVITY_ENTERS_BACKGROUND) { body() }
192
223
  }
193
224
 
225
+ /**
226
+ * Creates module's lifecycle listener that is called right after the activity is destroyed.
227
+ */
194
228
  inline fun onActivityDestroys(crossinline body: () -> Unit) {
195
229
  eventListeners[EventName.ACTIVITY_DESTROYS] = BasicEventListener(EventName.ACTIVITY_DESTROYS) { body() }
196
230
  }
@@ -203,23 +237,29 @@ class ModuleDefinitionBuilder {
203
237
  }
204
238
 
205
239
  /**
206
- * Method that is invoked when the first event listener is added.
240
+ * Creates module's lifecycle listener that is called right after the first event listener is added.
207
241
  */
208
242
  inline fun onStartObserving(crossinline body: () -> Unit) {
209
243
  function("startObserving", body)
210
244
  }
211
245
 
212
246
  /**
213
- * Method that is invoked when all event listeners are removed.
247
+ * Creates module's lifecycle listener that is called right after all event listeners are removed.
214
248
  */
215
249
  inline fun onStopObserving(crossinline body: () -> Unit) {
216
250
  function("stopObserving", body)
217
251
  }
218
252
 
253
+ /**
254
+ * Creates module's lifecycle listener that is called right after the new intent was received.
255
+ */
219
256
  inline fun onNewIntent(crossinline body: (Intent) -> Unit) {
220
257
  eventListeners[EventName.ON_NEW_INTENT] = EventListenerWithPayload<Intent>(EventName.ON_NEW_INTENT) { body(it) }
221
258
  }
222
259
 
260
+ /**
261
+ * Creates module's lifecycle listener that is called right after the activity has received a result.
262
+ */
223
263
  inline fun onActivityResult(crossinline body: (Activity, OnActivityResultPayload) -> Unit) {
224
264
  eventListeners[EventName.ON_ACTIVITY_RESULT] =
225
265
  EventListenerWithSenderAndPayload<Activity, OnActivityResultPayload>(EventName.ON_ACTIVITY_RESULT) { sender, payload -> body(sender, payload) }
@@ -1,3 +1,42 @@
1
1
  package expo.modules.kotlin.records
2
2
 
3
+ import android.os.Bundle
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.ReadableArray
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import java.lang.IllegalArgumentException
8
+ import kotlin.reflect.full.findAnnotation
9
+ import kotlin.reflect.full.memberProperties
10
+ import kotlin.reflect.jvm.isAccessible
11
+
3
12
  interface Record
13
+
14
+ fun Record.toJSMap(): ReadableMap {
15
+ val writableMap = Arguments.createMap()
16
+ javaClass
17
+ .kotlin
18
+ .memberProperties.map { property ->
19
+ val fieldInformation = property.findAnnotation<Field>() ?: return@map
20
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
21
+
22
+ property.isAccessible = true
23
+
24
+ // TODO(@lukmccall): add more sophisticated conversion method
25
+ when (val value = property.get(this)) {
26
+ null, is Unit -> writableMap.putNull(jsKey)
27
+ is Boolean -> writableMap.putBoolean(jsKey, value)
28
+ is Int -> writableMap.putInt(jsKey, value)
29
+ is Number -> writableMap.putDouble(jsKey, value.toDouble())
30
+ is String -> writableMap.putString(jsKey, value)
31
+ is ReadableArray -> writableMap.putArray(jsKey, value)
32
+ is ReadableMap -> writableMap.putMap(jsKey, value)
33
+ is Record -> writableMap.putMap(jsKey, value.toJSMap())
34
+ is Bundle -> writableMap.putMap(jsKey, Arguments.fromBundle(value))
35
+ is List<*> -> writableMap.putArray(jsKey, Arguments.fromList(value))
36
+ is Array<*> -> writableMap.putArray(jsKey, Arguments.fromArray(value))
37
+ else -> throw IllegalArgumentException("Could not convert " + value.javaClass)
38
+ }
39
+ }
40
+
41
+ return writableMap
42
+ }
@@ -3,6 +3,10 @@ package expo.modules.kotlin.records
3
3
  import com.facebook.react.bridge.Dynamic
4
4
  import expo.modules.kotlin.allocators.ObjectConstructor
5
5
  import expo.modules.kotlin.allocators.ObjectConstructorFactory
6
+ import expo.modules.kotlin.exception.FieldCastException
7
+ import expo.modules.kotlin.exception.RecordCastException
8
+ import expo.modules.kotlin.exception.exceptionDecorator
9
+ import expo.modules.kotlin.recycle
6
10
  import expo.modules.kotlin.types.TypeConverter
7
11
  import expo.modules.kotlin.types.TypeConverterProvider
8
12
  import kotlin.reflect.KClass
@@ -18,7 +22,7 @@ class RecordTypeConverter<T : Record>(
18
22
  ) : TypeConverter<T>(type.isMarkedNullable) {
19
23
  private val objectConstructorFactory = ObjectConstructorFactory()
20
24
 
21
- override fun convertNonOptional(value: Dynamic): T {
25
+ override fun convertNonOptional(value: Dynamic): T = exceptionDecorator({ cause -> RecordCastException(type, cause) }) {
22
26
  val jsMap = value.asMap()
23
27
 
24
28
  val kClass = type.classifier as KClass<*>
@@ -35,14 +39,17 @@ class RecordTypeConverter<T : Record>(
35
39
  return@map
36
40
  }
37
41
 
38
- val value = jsMap.getDynamic(jsKey)
39
- val javaField = property.javaField!!
42
+ jsMap.getDynamic(jsKey).recycle {
43
+ val javaField = property.javaField!!
40
44
 
41
- val elementConverter = converterProvider.obtainTypeConverter(property.returnType)
42
- val casted = elementConverter.convert(value)
45
+ val elementConverter = converterProvider.obtainTypeConverter(property.returnType)
46
+ val casted = exceptionDecorator({ cause -> FieldCastException(property.name, property.returnType, type, cause) }) {
47
+ elementConverter.convert(this)
48
+ }
43
49
 
44
- javaField.isAccessible = true
45
- javaField.set(instance, casted)
50
+ javaField.isAccessible = true
51
+ javaField.set(instance, casted)
52
+ }
46
53
  }
47
54
 
48
55
  @Suppress("UNCHECKED_CAST")
@@ -1,16 +1,18 @@
1
1
  package expo.modules.kotlin.types
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
+ import expo.modules.kotlin.exception.CollectionElementCastException
5
+ import expo.modules.kotlin.exception.exceptionDecorator
4
6
  import expo.modules.kotlin.recycle
5
7
  import kotlin.reflect.KClass
6
8
  import kotlin.reflect.KType
7
9
 
8
10
  class ArrayTypeConverter(
9
11
  converterProvider: TypeConverterProvider,
10
- private val type: KType,
11
- ) : TypeConverter<Array<*>>(type.isMarkedNullable) {
12
+ private val arrayType: KType,
13
+ ) : TypeConverter<Array<*>>(arrayType.isMarkedNullable) {
12
14
  private val arrayElementConverter = converterProvider.obtainTypeConverter(
13
- requireNotNull(type.arguments.first().type) {
15
+ requireNotNull(arrayType.arguments.first().type) {
14
16
  "The array type should contain the type of the elements."
15
17
  }
16
18
  )
@@ -22,7 +24,11 @@ class ArrayTypeConverter(
22
24
  array[i] = jsArray
23
25
  .getDynamic(i)
24
26
  .recycle {
25
- arrayElementConverter.convert(this)
27
+ exceptionDecorator({ cause ->
28
+ CollectionElementCastException(arrayType, arrayType.arguments.first().type!!, type, cause)
29
+ }) {
30
+ arrayElementConverter.convert(this)
31
+ }
26
32
  }
27
33
  }
28
34
  return array
@@ -37,7 +43,7 @@ class ArrayTypeConverter(
37
43
  @Suppress("UNCHECKED_CAST")
38
44
  private fun createTypedArray(size: Int): Array<Any?> {
39
45
  return java.lang.reflect.Array.newInstance(
40
- (type.arguments.first().type!!.classifier as KClass<*>).java,
46
+ (arrayType.arguments.first().type!!.classifier as KClass<*>).java,
41
47
  size
42
48
  ) as Array<Any?>
43
49
  }
@@ -1,15 +1,17 @@
1
1
  package expo.modules.kotlin.types
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
+ import expo.modules.kotlin.exception.CollectionElementCastException
5
+ import expo.modules.kotlin.exception.exceptionDecorator
4
6
  import expo.modules.kotlin.recycle
5
7
  import kotlin.reflect.KType
6
8
 
7
9
  class ListTypeConverter(
8
10
  converterProvider: TypeConverterProvider,
9
- type: KType,
10
- ) : TypeConverter<List<*>>(type.isMarkedNullable) {
11
+ private val listType: KType,
12
+ ) : TypeConverter<List<*>>(listType.isMarkedNullable) {
11
13
  private val elementConverter = converterProvider.obtainTypeConverter(
12
- requireNotNull(type.arguments.first().type) {
14
+ requireNotNull(listType.arguments.first().type) {
13
15
  "The list type should contain the type of elements."
14
16
  }
15
17
  )
@@ -18,7 +20,11 @@ class ListTypeConverter(
18
20
  val jsArray = value.asArray()
19
21
  return List(jsArray.size()) { index ->
20
22
  jsArray.getDynamic(index).recycle {
21
- elementConverter.convert(this)
23
+ exceptionDecorator({ cause ->
24
+ CollectionElementCastException(listType, listType.arguments.first().type!!, type, cause)
25
+ }) {
26
+ elementConverter.convert(this)
27
+ }
22
28
  }
23
29
  }
24
30
  }
@@ -4,22 +4,24 @@ package expo.modules.kotlin.types
4
4
 
5
5
  import com.facebook.react.bridge.Dynamic
6
6
  import com.facebook.react.bridge.DynamicFromObject
7
+ import expo.modules.kotlin.exception.CollectionElementCastException
8
+ import expo.modules.kotlin.exception.exceptionDecorator
7
9
  import expo.modules.kotlin.recycle
8
10
  import kotlin.reflect.KType
9
11
  import kotlin.reflect.typeOf
10
12
 
11
13
  class MapTypeConverter(
12
14
  converterProvider: TypeConverterProvider,
13
- type: KType
14
- ) : TypeConverter<Map<*, *>>(type.isMarkedNullable) {
15
+ private val mapType: KType
16
+ ) : TypeConverter<Map<*, *>>(mapType.isMarkedNullable) {
15
17
  init {
16
- require(type.arguments.first().type == typeOf<String>()) {
17
- "The map key type should be String, but received ${type.arguments.first()}."
18
+ require(mapType.arguments.first().type == typeOf<String>()) {
19
+ "The map key type should be String, but received ${mapType.arguments.first()}."
18
20
  }
19
21
  }
20
22
 
21
23
  private val valueConverter = converterProvider.obtainTypeConverter(
22
- requireNotNull(type.arguments.getOrNull(1)?.type) {
24
+ requireNotNull(mapType.arguments.getOrNull(1)?.type) {
23
25
  "The map type should contain the key type."
24
26
  }
25
27
  )
@@ -30,7 +32,11 @@ class MapTypeConverter(
30
32
 
31
33
  jsMap.entryIterator.forEach { (key, value) ->
32
34
  DynamicFromObject(value).recycle {
33
- result[key] = valueConverter.convert(this)
35
+ exceptionDecorator({ cause ->
36
+ CollectionElementCastException(mapType, mapType.arguments[1].type!!, type, cause)
37
+ }) {
38
+ result[key] = valueConverter.convert(this)
39
+ }
34
40
  }
35
41
  }
36
42
 
@@ -1,28 +1,44 @@
1
1
  package expo.modules.kotlin.types
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import expo.modules.kotlin.exception.CollectionElementCastException
6
+ import expo.modules.kotlin.exception.exceptionDecorator
7
+ import expo.modules.kotlin.recycle
4
8
  import kotlin.reflect.KType
5
9
 
6
10
  class PairTypeConverter(
7
11
  converterProvider: TypeConverterProvider,
8
- type: KType,
9
- ) : TypeConverter<Pair<*, *>>(type.isMarkedNullable) {
10
- private val firstConverter = converterProvider.obtainTypeConverter(
11
- requireNotNull(type.arguments.getOrNull(0)?.type) {
12
- "The pair type should contain the type of the first parameter."
13
- }
14
- )
15
- private val secondConverter = converterProvider.obtainTypeConverter(
16
- requireNotNull(type.arguments.getOrNull(1)?.type) {
17
- "The pair type should contain the type of the second parameter."
18
- }
12
+ private val pairType: KType,
13
+ ) : TypeConverter<Pair<*, *>>(pairType.isMarkedNullable) {
14
+ private val converters = listOf(
15
+ converterProvider.obtainTypeConverter(
16
+ requireNotNull(pairType.arguments.getOrNull(0)?.type) {
17
+ "The pair type should contain the type of the first parameter."
18
+ }
19
+ ),
20
+ converterProvider.obtainTypeConverter(
21
+ requireNotNull(pairType.arguments.getOrNull(1)?.type) {
22
+ "The pair type should contain the type of the second parameter."
23
+ }
24
+ )
19
25
  )
20
26
 
21
27
  override fun convertNonOptional(value: Dynamic): Pair<*, *> {
22
28
  val jsArray = value.asArray()
23
29
  return Pair(
24
- firstConverter.convert(jsArray.getDynamic(0)),
25
- secondConverter.convert(jsArray.getDynamic(1)),
30
+ convertElement(jsArray, 0),
31
+ convertElement(jsArray, 1)
26
32
  )
27
33
  }
34
+
35
+ private fun convertElement(array: ReadableArray, index: Int): Any? {
36
+ return array.getDynamic(index).recycle {
37
+ exceptionDecorator({ cause ->
38
+ CollectionElementCastException(pairType, pairType.arguments[index].type!!, type, cause)
39
+ }) {
40
+ converters[index].convert(this)
41
+ }
42
+ }
43
+ }
28
44
  }
@@ -1,6 +1,7 @@
1
1
  package expo.modules.kotlin.types
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
+ import expo.modules.kotlin.exception.NullArgumentException
4
5
 
5
6
  abstract class TypeConverter<Type : Any>(
6
7
  private val isOptional: Boolean
@@ -10,7 +11,7 @@ abstract class TypeConverter<Type : Any>(
10
11
  if (isOptional) {
11
12
  return null
12
13
  }
13
- throw IllegalArgumentException()
14
+ throw NullArgumentException()
14
15
  }
15
16
  return convertNonOptional(value)
16
17
  }
@@ -0,0 +1,3 @@
1
+ package expo.modules.kotlin.views
2
+
3
+ class CallbacksDefinition(val names: Array<out String>)
@@ -3,6 +3,7 @@ package expo.modules.kotlin.views
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
5
  import com.facebook.react.bridge.ReadableMap
6
+ import com.facebook.react.common.MapBuilder
6
7
  import com.facebook.react.uimanager.ThemedReactContext
7
8
  import com.facebook.react.uimanager.ViewGroupManager
8
9
  import com.facebook.react.uimanager.annotations.ReactProp
@@ -19,4 +20,25 @@ class GroupViewManagerWrapper(
19
20
  fun setProxiedProperties(view: View, proxiedProperties: ReadableMap) {
20
21
  viewWrapperDelegate.setProxiedProperties(view, proxiedProperties)
21
22
  }
23
+
24
+ override fun onDropViewInstance(view: ViewGroup) {
25
+ super.onDropViewInstance(view)
26
+ viewWrapperDelegate.onDestroy(view)
27
+ }
28
+
29
+ override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
30
+ viewWrapperDelegate.getExportedCustomDirectEventTypeConstants()?.let {
31
+ val directEvents = super.getExportedCustomDirectEventTypeConstants() ?: emptyMap()
32
+ val builder = MapBuilder.builder<String, Any>()
33
+ directEvents.forEach { event ->
34
+ builder.put(event.key, event.value)
35
+ }
36
+ it.forEach { event ->
37
+ builder.put(event.key, event.value)
38
+ }
39
+ return builder.build()
40
+ }
41
+
42
+ return super.getExportedCustomDirectEventTypeConstants()
43
+ }
22
44
  }
@@ -2,6 +2,7 @@ package expo.modules.kotlin.views
2
2
 
3
3
  import android.view.View
4
4
  import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.common.MapBuilder
5
6
  import com.facebook.react.uimanager.SimpleViewManager
6
7
  import com.facebook.react.uimanager.ThemedReactContext
7
8
  import com.facebook.react.uimanager.annotations.ReactProp
@@ -18,4 +19,25 @@ class SimpleViewManagerWrapper(
18
19
  fun setProxiedProperties(view: View, proxiedProperties: ReadableMap) {
19
20
  viewWrapperDelegate.setProxiedProperties(view, proxiedProperties)
20
21
  }
22
+
23
+ override fun onDropViewInstance(view: View) {
24
+ super.onDropViewInstance(view)
25
+ viewWrapperDelegate.onDestroy(view)
26
+ }
27
+
28
+ override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
29
+ viewWrapperDelegate.getExportedCustomDirectEventTypeConstants()?.let {
30
+ val directEvents = super.getExportedCustomDirectEventTypeConstants() ?: emptyMap()
31
+ val builder = MapBuilder.builder<String, Any>()
32
+ directEvents.forEach { event ->
33
+ builder.put(event.key, event.value)
34
+ }
35
+ it.forEach { event ->
36
+ builder.put(event.key, event.value)
37
+ }
38
+ return builder.build()
39
+ }
40
+
41
+ return super.getExportedCustomDirectEventTypeConstants()
42
+ }
21
43
  }
@@ -4,14 +4,20 @@ import android.content.Context
4
4
  import android.util.Log
5
5
  import android.view.View
6
6
  import android.view.ViewGroup
7
+ import com.facebook.react.bridge.ReactContext
7
8
  import com.facebook.react.bridge.ReadableMap
9
+ import expo.modules.adapters.react.NativeModulesProxy
8
10
  import expo.modules.core.ViewManager
11
+ import expo.modules.kotlin.exception.CodedException
12
+ import expo.modules.kotlin.exception.UnexpectedException
9
13
  import expo.modules.kotlin.recycle
10
14
 
11
15
  class ViewManagerDefinition(
12
16
  private val viewFactory: (Context) -> View,
13
17
  private val viewType: Class<out View>,
14
- private val props: Map<String, AnyViewProp>
18
+ private val props: Map<String, AnyViewProp>,
19
+ val onViewDestroys: ((View) -> Unit)? = null,
20
+ val callbacksDefinition: CallbacksDefinition? = null
15
21
  ) {
16
22
 
17
23
  fun createView(context: Context): View = viewFactory(context)
@@ -33,9 +39,28 @@ class ViewManagerDefinition(
33
39
  try {
34
40
  propDelegate.set(this, onView)
35
41
  } catch (exception: Throwable) {
36
- Log.e("ExpoModulesCore", "Cannot set the $key prop on the ${viewType.simpleName}.", exception)
42
+ Log.e("ExpoModulesCore", "Cannot set the '$key' prop on the '${viewType.simpleName}'.", exception)
43
+
44
+ handleException(
45
+ onView,
46
+ when (exception) {
47
+ is CodedException -> exception
48
+ else -> UnexpectedException(exception)
49
+ }
50
+ )
37
51
  }
38
52
  }
39
53
  }
40
54
  }
55
+
56
+ fun handleException(view: View, exception: CodedException) {
57
+ val reactContext = (view.context as? ReactContext) ?: return
58
+ val nativeModulesProxy = reactContext
59
+ .catalystInstance
60
+ ?.getNativeModule("NativeUnimoduleProxy") as? NativeModulesProxy
61
+ ?: return
62
+ val appContext = nativeModulesProxy.kotlinInteropModuleRegistry.appContext
63
+
64
+ appContext.errorManager?.reportExceptionToLogBox(exception)
65
+ }
41
66
  }
@@ -4,9 +4,11 @@ package expo.modules.kotlin.views
4
4
 
5
5
  import android.content.Context
6
6
  import android.view.View
7
+ import expo.modules.kotlin.modules.DefinitionMarker
7
8
  import expo.modules.kotlin.types.toAnyType
8
9
  import kotlin.reflect.typeOf
9
10
 
11
+ @DefinitionMarker
10
12
  class ViewManagerDefinitionBuilder {
11
13
  @PublishedApi
12
14
  internal var viewFactory: ((Context) -> View)? = null
@@ -14,19 +16,38 @@ class ViewManagerDefinitionBuilder {
14
16
  internal var viewType: Class<out View>? = null
15
17
  @PublishedApi
16
18
  internal var props = mutableMapOf<String, AnyViewProp>()
19
+ @PublishedApi
20
+ internal var onViewDestroys: ((View) -> Unit)? = null
21
+
22
+ private var callbacksDefinition: CallbacksDefinition? = null
17
23
 
18
24
  fun build(): ViewManagerDefinition =
19
25
  ViewManagerDefinition(
20
26
  requireNotNull(viewFactory),
21
27
  requireNotNull(viewType),
22
- props
28
+ props,
29
+ onViewDestroys,
30
+ callbacksDefinition
23
31
  )
24
32
 
33
+ /**
34
+ * Defines the factory creating a native view when the module is used as a view.
35
+ */
25
36
  inline fun <reified ViewType : View> view(noinline body: (Context) -> ViewType) {
26
37
  viewType = ViewType::class.java
27
38
  viewFactory = body
28
39
  }
29
40
 
41
+ /**
42
+ * Creates view's lifecycle listener that is called right after the view isn't longer used by React Native.
43
+ */
44
+ inline fun <reified ViewType : View> onViewDestroys(noinline body: (view: ViewType) -> Unit) {
45
+ onViewDestroys = { body(it as ViewType) }
46
+ }
47
+
48
+ /**
49
+ * Creates a view prop that defines its name and setter.
50
+ */
30
51
  inline fun <reified ViewType : View, reified PropType> prop(
31
52
  name: String,
32
53
  noinline body: (view: ViewType, prop: PropType) -> Unit
@@ -37,4 +58,11 @@ class ViewManagerDefinitionBuilder {
37
58
  body
38
59
  )
39
60
  }
61
+
62
+ /**
63
+ * Defines prop names that should be treated as callbacks.
64
+ */
65
+ fun events(vararg callbacks: String) {
66
+ callbacksDefinition = CallbacksDefinition(callbacks)
67
+ }
40
68
  }
@@ -1,9 +1,15 @@
1
1
  package expo.modules.kotlin.views
2
2
 
3
3
  import android.content.Context
4
+ import android.util.Log
4
5
  import android.view.View
5
6
  import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.common.MapBuilder
8
+ import expo.modules.core.utilities.ifNull
6
9
  import expo.modules.kotlin.ModuleHolder
10
+ import expo.modules.kotlin.callbacks.ViewCallbackDelegate
11
+ import kotlin.reflect.full.declaredMemberProperties
12
+ import kotlin.reflect.jvm.isAccessible
7
13
 
8
14
  class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder) {
9
15
  private val definition: ViewManagerDefinition
@@ -12,10 +18,61 @@ class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder) {
12
18
  val name: String
13
19
  get() = moduleHolder.name
14
20
 
15
- fun createView(context: Context): View =
16
- definition.createView(context)
21
+ fun createView(context: Context): View {
22
+ return definition
23
+ .createView(context)
24
+ .also {
25
+ configureView(it)
26
+ }
27
+ }
17
28
 
18
29
  fun setProxiedProperties(view: View, proxiedProperties: ReadableMap) {
19
30
  definition.setProps(proxiedProperties, view)
20
31
  }
32
+
33
+ fun onDestroy(view: View) =
34
+ definition.onViewDestroys?.invoke(view)
35
+
36
+ fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
37
+ val builder = MapBuilder.builder<String, Any>()
38
+ definition
39
+ .callbacksDefinition
40
+ ?.names
41
+ ?.forEach {
42
+ builder.put(
43
+ it, MapBuilder.of<String, Any>("registrationName", it)
44
+ )
45
+ }
46
+ return builder.build()
47
+ }
48
+
49
+ private fun configureView(view: View) {
50
+ val callbacks = definition.callbacksDefinition?.names ?: return
51
+
52
+ val kClass = view.javaClass.kotlin
53
+ val propertiesMap = kClass
54
+ .declaredMemberProperties
55
+ .map { it.name to it }
56
+ .toMap()
57
+
58
+ callbacks.forEach {
59
+ val property = propertiesMap[it].ifNull {
60
+ Log.w("ExpoModuleCore", "Property `$it` does not exist in ${kClass.simpleName}.")
61
+ return@forEach
62
+ }
63
+ property.isAccessible = true
64
+
65
+ val delegate = property.getDelegate(view).ifNull {
66
+ Log.w("ExpoModulesCore", "Property delegate for `$it` in ${kClass.simpleName} does not exist.")
67
+ return@forEach
68
+ }
69
+
70
+ val viewDelegate = (delegate as? ViewCallbackDelegate<*>).ifNull {
71
+ Log.w("ExpoModulesCore", "Property delegate for `$it` cannot be cased to `ViewCallbackDelegate`.")
72
+ return@forEach
73
+ }
74
+
75
+ viewDelegate.isValidated = true
76
+ }
77
+ }
21
78
  }