expo-modules-core 0.4.9 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +33 -2
  2. package/android/build.gradle +30 -2
  3. package/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java +42 -5
  4. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +49 -5
  5. package/android/src/main/java/expo/modules/core/BasePackage.java +6 -0
  6. package/android/src/main/java/expo/modules/core/ModulePriorities.kt +25 -0
  7. package/android/src/main/java/expo/modules/core/interfaces/ActivityEventListener.java +3 -1
  8. package/android/src/main/java/expo/modules/core/interfaces/Package.java +4 -0
  9. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.java +21 -0
  10. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityLifecycleListener.java +23 -0
  11. package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.java +14 -0
  12. package/android/src/main/java/expo/modules/core/utilities/KotlinUtilities.kt +23 -0
  13. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +166 -0
  14. package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +9 -0
  15. package/android/src/main/java/expo/modules/kotlin/ExpoModulesHelper.kt +18 -0
  16. package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +24 -0
  17. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +98 -0
  18. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +41 -0
  19. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +56 -0
  20. package/android/src/main/java/expo/modules/kotlin/ModulesProvider.kt +7 -0
  21. package/android/src/main/java/expo/modules/kotlin/Promise.kt +13 -0
  22. package/android/src/main/java/expo/modules/kotlin/ReactLifecycleDelegate.kt +39 -0
  23. package/android/src/main/java/expo/modules/kotlin/ReadableArrayIterator.kt +14 -0
  24. package/android/src/main/java/expo/modules/kotlin/ReadableTypeExtensions.kt +18 -0
  25. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructor.kt +5 -0
  26. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructorFactory.kt +31 -0
  27. package/android/src/main/java/expo/modules/kotlin/allocators/UnsafeAllocator.kt +49 -0
  28. package/android/src/main/java/expo/modules/kotlin/events/EventListener.kt +39 -0
  29. package/android/src/main/java/expo/modules/kotlin/events/EventName.kt +31 -0
  30. package/android/src/main/java/expo/modules/kotlin/events/EventsDefinition.kt +3 -0
  31. package/android/src/main/java/expo/modules/kotlin/events/KEventEmitterWrapper.kt +26 -0
  32. package/android/src/main/java/expo/modules/kotlin/events/OnActivityResultPayload.kt +8 -0
  33. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +70 -0
  34. package/android/src/main/java/expo/modules/kotlin/methods/AnyMethod.kt +50 -0
  35. package/android/src/main/java/expo/modules/kotlin/methods/Method.kt +14 -0
  36. package/android/src/main/java/expo/modules/kotlin/methods/PromiseMethod.kt +15 -0
  37. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +24 -0
  38. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +227 -0
  39. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +16 -0
  40. package/android/src/main/java/expo/modules/kotlin/records/Field.kt +5 -0
  41. package/android/src/main/java/expo/modules/kotlin/records/Record.kt +3 -0
  42. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +55 -0
  43. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +14 -0
  44. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +44 -0
  45. package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +60 -0
  46. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +84 -0
  47. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +25 -0
  48. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +39 -0
  49. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +28 -0
  50. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +19 -0
  51. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +107 -0
  52. package/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +10 -0
  53. package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +17 -0
  54. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +22 -0
  55. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +21 -0
  56. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +41 -0
  57. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +40 -0
  58. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +21 -0
  59. package/android/src/main/java/expo/modules/kotlin/views/ViewWrapperDelegateHolder.kt +5 -0
  60. package/build/NativeModulesProxy.native.d.ts +4 -0
  61. package/build/NativeModulesProxy.native.js +14 -1
  62. package/build/NativeModulesProxy.native.js.map +1 -1
  63. package/build/NativeModulesProxy.types.d.ts +3 -0
  64. package/build/NativeModulesProxy.types.js.map +1 -1
  65. package/build/NativeViewManagerAdapter.native.js +1 -1
  66. package/build/NativeViewManagerAdapter.native.js.map +1 -1
  67. package/ios/AppDelegates/EXAppDelegateWrapper.h +19 -0
  68. package/ios/AppDelegates/EXAppDelegateWrapper.m +45 -0
  69. package/ios/AppDelegates/EXAppDelegatesLoader.h +15 -0
  70. package/ios/AppDelegates/EXAppDelegatesLoader.m +30 -0
  71. package/ios/AppDelegates/EXLegacyAppDelegateWrapper.h +16 -0
  72. package/ios/{EXAppDelegateWrapper.m → AppDelegates/EXLegacyAppDelegateWrapper.m} +2 -2
  73. package/ios/AppDelegates/ExpoAppDelegate.swift +282 -0
  74. package/ios/AppDelegates/ExpoAppDelegateSubscriber.swift +24 -0
  75. package/ios/EXAppDefines.h +26 -0
  76. package/ios/EXAppDefines.m +61 -0
  77. package/ios/ExpoModulesCore.podspec +8 -3
  78. package/ios/JSI/ExpoModulesProxySpec.h +24 -0
  79. package/ios/JSI/ExpoModulesProxySpec.mm +135 -0
  80. package/ios/JSI/JSIConversions.h +42 -0
  81. package/ios/JSI/JSIConversions.mm +164 -0
  82. package/ios/JSI/JSIInstaller.h +19 -0
  83. package/ios/JSI/JSIInstaller.mm +22 -0
  84. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +1 -6
  85. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +6 -0
  86. package/ios/NativeModulesProxy/{EXNativeModulesProxy.m → EXNativeModulesProxy.mm} +63 -17
  87. package/ios/ReactDelegates/EXRCTBridgeDelegateInterceptor.h +16 -0
  88. package/ios/ReactDelegates/EXRCTBridgeDelegateInterceptor.m +49 -0
  89. package/ios/ReactDelegates/EXReactDelegateWrapper+Private.h +18 -0
  90. package/ios/ReactDelegates/EXReactDelegateWrapper.h +25 -0
  91. package/ios/ReactDelegates/EXReactDelegateWrapper.m +40 -0
  92. package/ios/ReactDelegates/ExpoReactDelegate.swift +37 -0
  93. package/ios/ReactDelegates/ExpoReactDelegateHandler.swift +52 -0
  94. package/ios/ReactDelegates/ModulePriorities.swift +20 -0
  95. package/ios/Services/EXReactNativeEventEmitter.h +6 -0
  96. package/ios/Services/EXReactNativeEventEmitter.m +15 -0
  97. package/ios/Swift/AppContext.swift +14 -1
  98. package/ios/Swift/Arguments/AnyArgument.swift +14 -0
  99. package/ios/Swift/Arguments/AnyArgumentType.swift +13 -0
  100. package/ios/Swift/Arguments/ArgumentType.swift +24 -0
  101. package/ios/Swift/Arguments/ConvertibleArgument.swift +15 -0
  102. package/ios/Swift/Arguments/Convertibles.swift +107 -0
  103. package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +42 -0
  104. package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +16 -0
  105. package/ios/Swift/Arguments/Types/EnumArgumentType.swift +105 -0
  106. package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +49 -0
  107. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +15 -0
  108. package/ios/Swift/Arguments/Types/RawArgumentType.swift +25 -0
  109. package/ios/Swift/Conversions.swift +199 -7
  110. package/ios/Swift/EventListener.swift +37 -5
  111. package/ios/Swift/Functions/AnyFunction.swift +42 -0
  112. package/ios/Swift/{Methods/ConcreteMethod.swift → Functions/ConcreteFunction.swift} +32 -34
  113. package/ios/Swift/ModuleHolder.swift +86 -20
  114. package/ios/Swift/ModuleRegistry.swift +19 -8
  115. package/ios/Swift/Modules/AnyModule.swift +8 -8
  116. package/ios/Swift/Modules/Module.swift +11 -0
  117. package/ios/Swift/Modules/ModuleDefinition.swift +55 -15
  118. package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +1 -1
  119. package/ios/Swift/Modules/ModuleDefinitionComponents.swift +149 -54
  120. package/ios/Swift/ModulesProvider.swift +19 -0
  121. package/ios/Swift/Promise.swift +1 -1
  122. package/ios/Swift/Records/Field.swift +1 -1
  123. package/ios/Swift/Records/Record.swift +8 -1
  124. package/ios/Swift/SwiftInteropBridge.swift +46 -17
  125. package/ios/Swift/Views/AnyViewProp.swift +2 -2
  126. package/ios/Swift/Views/ConcreteViewProp.swift +37 -10
  127. package/ios/Swift/Views/ViewModuleWrapper.swift +9 -4
  128. package/ios/Swift.h +9 -0
  129. package/ios/Tests/ArgumentTypeSpec.swift +145 -0
  130. package/ios/Tests/ConstantsSpec.swift +36 -0
  131. package/ios/Tests/ConvertiblesSpec.swift +265 -0
  132. package/ios/Tests/EXAppDefinesTest.m +99 -0
  133. package/ios/Tests/{MethodSpec.swift → FunctionSpec.swift} +69 -54
  134. package/ios/Tests/FunctionWithConvertiblesSpec.swift +66 -0
  135. package/ios/Tests/Mocks/ModuleMocks.swift +21 -7
  136. package/ios/Tests/ModuleEventListenersSpec.swift +17 -16
  137. package/ios/Tests/ModuleRegistrySpec.swift +4 -7
  138. package/package.json +3 -3
  139. package/src/NativeModulesProxy.native.ts +22 -2
  140. package/src/NativeModulesProxy.types.ts +8 -0
  141. package/src/NativeViewManagerAdapter.native.tsx +1 -1
  142. package/ios/EXAppDelegateWrapper.h +0 -13
  143. package/ios/Swift/Methods/AnyArgumentType.swift +0 -48
  144. package/ios/Swift/Methods/AnyMethod.swift +0 -31
  145. package/ios/Swift/Methods/AnyMethodArgument.swift +0 -13
@@ -0,0 +1,18 @@
1
+ package expo.modules.kotlin
2
+
3
+ import android.util.Log
4
+
5
+ class ExpoModulesHelper {
6
+ companion object {
7
+ @Suppress("unchecked_cast")
8
+ val modulesProvider by lazy {
9
+ try {
10
+ val expoModules = Class.forName("expo.modules.ExpoModulesPackageList")
11
+ expoModules.newInstance() as ModulesProvider
12
+ } catch (e: Exception) {
13
+ Log.e("ExpoModulesHelper", "Couldn't get expo modules list.", e)
14
+ null
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,24 @@
1
+ package expo.modules.kotlin
2
+
3
+ import android.os.Bundle
4
+ import com.facebook.react.bridge.Arguments
5
+
6
+ class KPromiseWrapper(
7
+ private val bridgePromise: com.facebook.react.bridge.Promise
8
+ ) : Promise {
9
+
10
+ override fun resolve(value: Any?) {
11
+ bridgePromise.resolve(
12
+ when (value) {
13
+ is Unit -> null
14
+ is Bundle -> Arguments.fromBundle(value as Bundle?)
15
+ is List<*> -> Arguments.fromList(value as List<*>?)
16
+ else -> value
17
+ }
18
+ )
19
+ }
20
+
21
+ override fun reject(code: String, message: String?, cause: Throwable?) {
22
+ bridgePromise.reject(code, message, cause)
23
+ }
24
+ }
@@ -0,0 +1,98 @@
1
+ package expo.modules.kotlin
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import com.facebook.react.uimanager.ViewManager
6
+ import expo.modules.kotlin.views.GroupViewManagerWrapper
7
+ import expo.modules.kotlin.views.SimpleViewManagerWrapper
8
+ import expo.modules.kotlin.views.ViewManagerWrapperDelegate
9
+ import expo.modules.kotlin.views.ViewWrapperDelegateHolder
10
+ import java.lang.ref.WeakReference
11
+
12
+ private typealias ModuleName = String
13
+ private typealias ModuleConstants = Map<String, Any?>
14
+ private typealias ModuleMethodInfo = Map<String, Any?>
15
+
16
+ class KotlinInteropModuleRegistry(
17
+ modulesProvider: ModulesProvider,
18
+ legacyModuleRegistry: expo.modules.core.ModuleRegistry,
19
+ reactContext: WeakReference<ReactApplicationContext>
20
+ ) {
21
+ private val appContext = AppContext(modulesProvider, legacyModuleRegistry, reactContext)
22
+
23
+ private val registry: ModuleRegistry
24
+ get() = appContext.registry
25
+
26
+ fun hasModule(name: String): Boolean = registry.hasModule(name)
27
+
28
+ fun callMethod(moduleName: String, method: String, arguments: ReadableArray, promise: Promise) {
29
+ registry
30
+ .getModuleHolder(moduleName)
31
+ ?.call(method, arguments, promise)
32
+ }
33
+
34
+ fun exportedModulesConstants(): Map<ModuleName, ModuleConstants> {
35
+ return registry
36
+ .map { holder ->
37
+ holder.name to holder.definition.constantsProvider()
38
+ }
39
+ .toMap()
40
+ }
41
+
42
+ fun exportMethods(exportKey: (String, List<ModuleMethodInfo>) -> Unit = { _, _ -> }): Map<ModuleName, List<ModuleMethodInfo>> {
43
+ return registry
44
+ .map { holder ->
45
+ val methodsInfo = holder.definition.methods.map { (name, method) ->
46
+ mapOf(
47
+ "name" to name,
48
+ "argumentsCount" to method.argsCount
49
+ )
50
+ }
51
+ exportKey(holder.name, methodsInfo)
52
+ holder.name to methodsInfo
53
+ }
54
+ .toMap()
55
+ }
56
+
57
+ fun exportViewManagers(): List<ViewManager<*, *>> {
58
+ return registry
59
+ .filter { it.definition.viewManagerDefinition != null }
60
+ .map {
61
+ val wrapperDelegate = ViewManagerWrapperDelegate(it)
62
+ when (it.definition.viewManagerDefinition!!.getViewManagerType()) {
63
+ expo.modules.core.ViewManager.ViewManagerType.SIMPLE -> SimpleViewManagerWrapper(wrapperDelegate)
64
+ expo.modules.core.ViewManager.ViewManagerType.GROUP -> GroupViewManagerWrapper(wrapperDelegate)
65
+ }
66
+ }
67
+ }
68
+
69
+ fun extractViewManagersDelegateHolders(viewManagers: List<ViewManager<*, *>>): List<ViewWrapperDelegateHolder> =
70
+ viewManagers.filterIsInstance<ViewWrapperDelegateHolder>()
71
+
72
+ /**
73
+ * Since React Native v0.55, {@link com.facebook.react.ReactPackage#createViewManagers(ReactApplicationContext)}
74
+ * gets called only once per lifetime of {@link com.facebook.react.ReactInstanceManager}.
75
+ * However, in our new architecture, users can make a bind between ViewManager and module
76
+ * (for instance, they can use `this` in prop definition). This will lead to bugs, cause
77
+ * the instance that was bound with the prop method won't be the same as the instance returned by module registry.
78
+ * To fix that we need to update all modules holder in exported view managers.
79
+ */
80
+ fun updateModuleHoldersInViewManagers(viewWrapperHolders: List<ViewWrapperDelegateHolder>) {
81
+ viewWrapperHolders
82
+ .map { it.viewWrapperDelegate }
83
+ .forEach { holderWrapper ->
84
+ holderWrapper.moduleHolder = requireNotNull(registry.getModuleHolder(holderWrapper.moduleHolder.name)) {
85
+ "Cannot update the module holder for ${holderWrapper.moduleHolder.name}."
86
+ }
87
+ }
88
+ }
89
+
90
+ fun exportedViewManagersNames(): List<String> =
91
+ registry
92
+ .filter { it.definition.viewManagerDefinition != null }
93
+ .map { it.definition.name }
94
+
95
+ fun onDestroy() {
96
+ appContext.onDestroy()
97
+ }
98
+ }
@@ -0,0 +1,41 @@
1
+ package expo.modules.kotlin
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import expo.modules.core.utilities.ifNull
5
+ import expo.modules.kotlin.events.BasicEventListener
6
+ import expo.modules.kotlin.events.EventListenerWithPayload
7
+ import expo.modules.kotlin.events.EventListenerWithSenderAndPayload
8
+ import expo.modules.kotlin.events.EventName
9
+ import expo.modules.kotlin.exception.MethodNotFoundException
10
+ import expo.modules.kotlin.modules.Module
11
+
12
+ class ModuleHolder(val module: Module) {
13
+ val definition = module.definition()
14
+ val name get() = definition.name
15
+
16
+ fun call(methodName: String, args: ReadableArray, promise: Promise) {
17
+ val method = definition.methods[methodName].ifNull {
18
+ promise.reject(MethodNotFoundException(methodName, definition.name))
19
+ return
20
+ }
21
+
22
+ method.call(args, promise)
23
+ }
24
+
25
+ fun post(eventName: EventName) {
26
+ val listener = definition.eventListeners[eventName] ?: return
27
+ (listener as? BasicEventListener)?.call()
28
+ }
29
+
30
+ @Suppress("UNCHECKED_CAST")
31
+ fun <Payload> post(eventName: EventName, payload: Payload) {
32
+ val listener = definition.eventListeners[eventName] ?: return
33
+ (listener as? EventListenerWithPayload<Payload>)?.call(payload)
34
+ }
35
+
36
+ @Suppress("UNCHECKED_CAST")
37
+ fun <Sender, Payload> post(eventName: EventName, sender: Sender, payload: Payload) {
38
+ val listener = definition.eventListeners[eventName] ?: return
39
+ (listener as? EventListenerWithSenderAndPayload<Sender, Payload>)?.call(sender, payload)
40
+ }
41
+ }
@@ -0,0 +1,56 @@
1
+ package expo.modules.kotlin
2
+
3
+ import expo.modules.kotlin.events.EventName
4
+ import expo.modules.kotlin.modules.Module
5
+ import java.lang.ref.WeakReference
6
+
7
+ class ModuleRegistry(
8
+ private val appContext: WeakReference<AppContext>
9
+ ) : Iterable<ModuleHolder> {
10
+ private val registry = mutableMapOf<String, ModuleHolder>()
11
+
12
+ fun register(module: Module) {
13
+ val holder = ModuleHolder(module)
14
+ module._appContext = requireNotNull(appContext.get()) { "Cannot create a module for invalid app context." }
15
+ holder.post(EventName.MODULE_CREATE)
16
+ registry[holder.name] = holder
17
+ }
18
+
19
+ fun register(provider: ModulesProvider) = apply {
20
+ provider.getModulesList().forEach { type ->
21
+ val module = type.newInstance()
22
+ register(module)
23
+ }
24
+ }
25
+
26
+ fun hasModule(name: String): Boolean = registry.containsKey(name)
27
+
28
+ fun getModule(name: String): Module? = registry[name]?.module
29
+
30
+ fun getModuleHolder(name: String): ModuleHolder? = registry[name]
31
+
32
+ fun getModuleHolder(module: Module): ModuleHolder? =
33
+ registry.values.find { it.module === module }
34
+
35
+ fun post(eventName: EventName) {
36
+ forEach {
37
+ it.post(eventName)
38
+ }
39
+ }
40
+
41
+ @Suppress("UNCHECKED_CAST")
42
+ fun <Sender> post(eventName: EventName, sender: Sender) {
43
+ forEach {
44
+ it.post(eventName, sender)
45
+ }
46
+ }
47
+
48
+ @Suppress("UNCHECKED_CAST")
49
+ fun <Sender, Payload> post(eventName: EventName, sender: Sender, payload: Payload) {
50
+ forEach {
51
+ it.post(eventName, sender, payload)
52
+ }
53
+ }
54
+
55
+ override fun iterator(): Iterator<ModuleHolder> = registry.values.iterator()
56
+ }
@@ -0,0 +1,7 @@
1
+ package expo.modules.kotlin
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+
5
+ interface ModulesProvider {
6
+ fun getModulesList(): List<Class<out Module>>
7
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.kotlin
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ interface Promise {
6
+ fun resolve(value: Any?)
7
+
8
+ fun reject(code: String, message: String?, cause: Throwable?)
9
+
10
+ fun reject(exception: CodedException) {
11
+ reject(exception.code, exception.message, exception.cause)
12
+ }
13
+ }
@@ -0,0 +1,39 @@
1
+ package expo.modules.kotlin
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import com.facebook.react.bridge.ActivityEventListener
6
+ import com.facebook.react.bridge.LifecycleEventListener
7
+ import java.lang.ref.WeakReference
8
+
9
+ /**
10
+ * We had to extract this from AppContext, because otherwise, we can't access AppContext
11
+ * in a package that doesn't depend on the React Native directly.
12
+ * Due to:
13
+ * Cannot access 'com.facebook.react.bridge.LifecycleEventListener'
14
+ * which is a supertype of 'expo.modules.kotlin.AppContext'.
15
+ * Check your module classpath for missing or conflicting dependencies
16
+ */
17
+ class ReactLifecycleDelegate(appContext: AppContext) : LifecycleEventListener, ActivityEventListener {
18
+ private val appContextHolder = WeakReference(appContext)
19
+
20
+ override fun onHostResume() {
21
+ appContextHolder.get()?.onHostResume()
22
+ }
23
+
24
+ override fun onHostPause() {
25
+ appContextHolder.get()?.onHostPause()
26
+ }
27
+
28
+ override fun onHostDestroy() {
29
+ appContextHolder.get()?.onHostDestroy()
30
+ }
31
+
32
+ override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
33
+ appContextHolder.get()?.onActivityResult(activity, requestCode, resultCode, data)
34
+ }
35
+
36
+ override fun onNewIntent(intent: Intent?) {
37
+ appContextHolder.get()?.onNewIntent(intent)
38
+ }
39
+ }
@@ -0,0 +1,14 @@
1
+ package expo.modules.kotlin
2
+
3
+ import com.facebook.react.bridge.Dynamic
4
+ import com.facebook.react.bridge.ReadableArray
5
+
6
+ class ReadableArrayIterator(private val array: ReadableArray) : Iterator<Dynamic> {
7
+ var current = 0
8
+
9
+ override fun hasNext(): Boolean = current < array.size()
10
+
11
+ override fun next(): Dynamic = array.getDynamic(current++)
12
+ }
13
+
14
+ fun ReadableArray.iterator(): ReadableArrayIterator = ReadableArrayIterator(this)
@@ -0,0 +1,18 @@
1
+ package expo.modules.kotlin
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.bridge.ReadableType
6
+ import kotlin.reflect.KType
7
+ import kotlin.reflect.full.createType
8
+
9
+ fun ReadableType.toKType(): KType {
10
+ return when (this) {
11
+ ReadableType.Null -> Any::class.createType(nullable = true)
12
+ ReadableType.Boolean -> Boolean::class.createType()
13
+ ReadableType.Number -> Number::class.createType()
14
+ ReadableType.String -> String::class.createType()
15
+ ReadableType.Map -> ReadableMap::class.createType()
16
+ ReadableType.Array -> ReadableArray::class.createType()
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ package expo.modules.kotlin.allocators
2
+
3
+ fun interface ObjectConstructor<T> {
4
+ fun construct(): T
5
+ }
@@ -0,0 +1,31 @@
1
+ package expo.modules.kotlin.allocators
2
+
3
+ /**
4
+ * This class was created based on https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java.
5
+ */
6
+ class ObjectConstructorFactory {
7
+ fun <T> get(clazz: Class<T>): ObjectConstructor<T> =
8
+ tryToUseDefaultConstructor(clazz) ?: useUnsafeAllocator(clazz)
9
+
10
+ private fun <T> tryToUseDefaultConstructor(clazz: Class<T>): ObjectConstructor<T>? {
11
+ return try {
12
+ val ctor = clazz.getDeclaredConstructor()
13
+ if (!ctor.isAccessible) {
14
+ ctor.isAccessible = true
15
+ }
16
+
17
+ ObjectConstructor {
18
+ ctor.newInstance() as T
19
+ }
20
+ } catch (e: NoSuchMethodException) {
21
+ null
22
+ }
23
+ }
24
+
25
+ private fun <T> useUnsafeAllocator(clazz: Class<T>): ObjectConstructor<T> {
26
+ val allocator = UnsafeAllocator.createAllocator(clazz)
27
+ return ObjectConstructor {
28
+ allocator.newInstance()
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,49 @@
1
+ package expo.modules.kotlin.allocators
2
+
3
+ import android.annotation.SuppressLint
4
+ import java.io.ObjectStreamClass
5
+ import kotlin.jvm.Throws
6
+
7
+ /**
8
+ * Base on https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java.
9
+ */
10
+ fun interface UnsafeAllocator<T> {
11
+ @Throws(Exception::class)
12
+ fun newInstance(): T
13
+
14
+ companion object {
15
+ @SuppressLint("DiscouragedPrivateApi")
16
+ @Suppress("UNCHECKED_CAST")
17
+ fun <T> createAllocator(clazz: Class<T>): UnsafeAllocator<T> {
18
+ // try DalvikVM
19
+ try {
20
+ val getConstructorId = ObjectStreamClass::class.java.getDeclaredMethod("getConstructorId", Class::class.java)
21
+ getConstructorId.isAccessible = true
22
+ val constructorId = getConstructorId.invoke(null, Object::class.java) as Int
23
+ val newInstance = ObjectStreamClass::class.java.getDeclaredMethod("newInstance", Class::class.java, Int::class.java)
24
+ newInstance.isAccessible = true
25
+ return UnsafeAllocator {
26
+ newInstance.invoke(null, clazz, constructorId) as T
27
+ }
28
+ } catch (_: Throwable) {
29
+ }
30
+
31
+ // try JVM (for unit tests)
32
+ try {
33
+ val unsafeClass = Class.forName("sun.misc.Unsafe")
34
+ val theUnsafe = unsafeClass.getDeclaredField("theUnsafe")
35
+ theUnsafe.isAccessible = true
36
+ val unsafeObj = theUnsafe.get(null)
37
+ val allocateInstance = unsafeClass.getMethod("allocateInstance", Class::class.java)
38
+ return UnsafeAllocator {
39
+ allocateInstance.invoke(unsafeObj, clazz) as T
40
+ }
41
+ } catch (_: Throwable) {
42
+ }
43
+
44
+ return UnsafeAllocator {
45
+ throw IllegalArgumentException("Cannot allocate $clazz")
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,39 @@
1
+ package expo.modules.kotlin.events
2
+
3
+ sealed class EventListener(val eventName: EventName)
4
+
5
+ /**
6
+ * Listener for events without sender and payload.
7
+ */
8
+ class BasicEventListener(
9
+ eventName: EventName,
10
+ val body: () -> Unit
11
+ ) : EventListener(eventName) {
12
+ fun call() {
13
+ body()
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Listener for events with payload.
19
+ */
20
+ class EventListenerWithPayload<Payload>(
21
+ eventName: EventName,
22
+ val body: (Payload) -> Unit
23
+ ) : EventListener(eventName) {
24
+ fun call(sender: Payload) {
25
+ body(sender)
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Listener for events that specify sender and payload.
31
+ */
32
+ class EventListenerWithSenderAndPayload<Sender, Payload>(
33
+ eventName: EventName,
34
+ val body: (Sender, Payload) -> Unit
35
+ ) : EventListener(eventName) {
36
+ fun call(sender: Sender, payload: Payload) {
37
+ body(sender, payload)
38
+ }
39
+ }
@@ -0,0 +1,31 @@
1
+ package expo.modules.kotlin.events
2
+
3
+ enum class EventName {
4
+ MODULE_CREATE,
5
+ MODULE_DESTROY,
6
+
7
+ /**
8
+ * Called when the host activity receives a resume event (e.g. Activity.onResume)
9
+ */
10
+ ACTIVITY_ENTERS_FOREGROUND,
11
+
12
+ /**
13
+ * Called when host activity receives pause event (e.g. Activity.onPause)
14
+ */
15
+ ACTIVITY_ENTERS_BACKGROUND,
16
+
17
+ /**
18
+ * Called when host activity receives destroy event (e.g. Activity.onDestroy)
19
+ */
20
+ ACTIVITY_DESTROYS,
21
+
22
+ /**
23
+ * Called when a new intent is passed to the activity
24
+ */
25
+ ON_NEW_INTENT,
26
+
27
+ /**
28
+ * Called when other activity returns
29
+ */
30
+ ON_ACTIVITY_RESULT
31
+ }
@@ -0,0 +1,3 @@
1
+ package expo.modules.kotlin.events
2
+
3
+ class EventsDefinition(val names: Array<out String>)
@@ -0,0 +1,26 @@
1
+ package expo.modules.kotlin.events
2
+
3
+ import android.os.Bundle
4
+ import expo.modules.core.interfaces.services.EventEmitter
5
+ import expo.modules.kotlin.ModuleHolder
6
+
7
+ /**
8
+ * In Swift, we check if the event is supported. Otherwise, we can't send such event through the bridge.
9
+ * However, in Kotlin this is unnecessary, but to make our API consistent, we added similar check.
10
+ * But because of that, we had to create a wrapper for EventEmitter.
11
+ */
12
+ class KEventEmitterWrapper(
13
+ private val moduleHolder: ModuleHolder,
14
+ private val legacyEventEmitter: EventEmitter
15
+ ) : EventEmitter by legacyEventEmitter {
16
+ override fun emit(eventName: String, eventBody: Bundle?) {
17
+ require(
18
+ moduleHolder
19
+ .definition
20
+ .eventsDefinition
21
+ ?.names
22
+ ?.contains(eventName) == true
23
+ ) { "Unsupported event: $eventName." }
24
+ legacyEventEmitter.emit(eventName, eventBody)
25
+ }
26
+ }
@@ -0,0 +1,8 @@
1
+ package expo.modules.kotlin.events
2
+
3
+ import android.content.Intent
4
+
5
+ /**
6
+ * Payload for the `onActivityResult` event.
7
+ */
8
+ data class OnActivityResultPayload(val requestCode: Int, val resultCode: Int, val data: Intent?)
@@ -0,0 +1,70 @@
1
+ package expo.modules.kotlin.exception
2
+
3
+ import java.util.*
4
+ import kotlin.reflect.KType
5
+
6
+ /**
7
+ * A class for errors specifying its `code` and providing the `description`.
8
+ */
9
+ open class CodedException(
10
+ message: String?,
11
+ cause: Throwable?
12
+ ) : Exception(message, cause) {
13
+ // We need that secondary property, cause we can't access
14
+ // the javaClass property in the constructor.
15
+ private var providedCode: String? = null
16
+
17
+ val code
18
+ get() = providedCode ?: inferCode(javaClass)
19
+
20
+ constructor(code: String, message: String, cause: Throwable?) : this(message, cause) {
21
+ providedCode = code
22
+ }
23
+
24
+ constructor(message: String) : this(message, null)
25
+
26
+ constructor(cause: Throwable) : this(cause.localizedMessage, cause)
27
+
28
+ constructor() : this(null, null)
29
+
30
+ companion object {
31
+ /**
32
+ * The code is inferred from the class name — e.g. the code of `ModuleNotFoundException` becomes `ERR_MODULE_NOT_FOUND`.
33
+ */
34
+ private fun inferCode(clazz: Class<*>): String {
35
+ val name = requireNotNull(clazz.simpleName) { "Cannot infer code name from class name. We don't support anonymous classes." }
36
+
37
+ return "ERR_" + name
38
+ .replace("(Exception)$".toRegex(), "")
39
+ .replace("(.)([A-Z])".toRegex(), "$1_$2")
40
+ .toUpperCase(Locale.ROOT)
41
+ }
42
+ }
43
+ }
44
+
45
+ internal class IncompatibleArgTypeException(
46
+ argumentType: KType,
47
+ desiredType: KType,
48
+ cause: Throwable? = null
49
+ ) : CodedException(
50
+ message = "Argument type $argumentType is not compatible with expected type $desiredType.",
51
+ cause = cause
52
+ )
53
+
54
+ internal class MissingTypeConverter(
55
+ forType: KType
56
+ ) : CodedException(
57
+ message = "Cannot find type converter for $forType.",
58
+ )
59
+
60
+ internal class InvalidArgsNumberException(received: Int, expected: Int) :
61
+ CodedException(message = "Received $received arguments, but $expected was expected.")
62
+
63
+ internal class MethodNotFoundException(methodName: String, moduleName: String) :
64
+ CodedException(message = "Cannot fund method $methodName in module $moduleName")
65
+
66
+ internal class NullArgumentException(desiredType: KType) :
67
+ CodedException(message = "Cannot assigned null to not nullable type $desiredType")
68
+
69
+ internal class UnexpectedException(val throwable: Throwable) :
70
+ CodedException(throwable)
@@ -0,0 +1,50 @@
1
+ package expo.modules.kotlin.methods
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import expo.modules.kotlin.Promise
5
+ import expo.modules.kotlin.exception.CodedException
6
+ import expo.modules.kotlin.exception.InvalidArgsNumberException
7
+ import expo.modules.kotlin.exception.UnexpectedException
8
+ import expo.modules.kotlin.iterator
9
+ import expo.modules.kotlin.recycle
10
+ import expo.modules.kotlin.types.AnyType
11
+ import kotlin.jvm.Throws
12
+
13
+ abstract class AnyMethod(
14
+ protected val name: String,
15
+ private val desiredArgsTypes: Array<AnyType>
16
+ ) {
17
+ fun call(args: ReadableArray, promise: Promise) {
18
+ if (desiredArgsTypes.size != args.size()) {
19
+ promise.reject(InvalidArgsNumberException(args.size(), desiredArgsTypes.size))
20
+ return
21
+ }
22
+ try {
23
+ val convertedArgs = convertArgs(args)
24
+ callImplementation(convertedArgs, promise)
25
+ } catch (codedError: CodedException) {
26
+ promise.reject(codedError)
27
+ } catch (e: Throwable) {
28
+ promise.reject(UnexpectedException(e))
29
+ }
30
+ }
31
+
32
+ @Throws(CodedException::class)
33
+ internal abstract fun callImplementation(args: Array<out Any?>, promise: Promise)
34
+
35
+ val argsCount get() = desiredArgsTypes.size
36
+
37
+ @Throws(CodedException::class)
38
+ private fun convertArgs(args: ReadableArray): Array<out Any?> {
39
+ val finalArgs = Array<Any?>(desiredArgsTypes.size) { null }
40
+ val argIterator = args.iterator()
41
+ desiredArgsTypes
42
+ .withIndex()
43
+ .forEach { (index, type) ->
44
+ argIterator.next().recycle {
45
+ finalArgs[index] = type.convert(this)
46
+ }
47
+ }
48
+ return finalArgs
49
+ }
50
+ }
@@ -0,0 +1,14 @@
1
+ package expo.modules.kotlin.methods
2
+
3
+ import expo.modules.kotlin.Promise
4
+ import expo.modules.kotlin.types.AnyType
5
+
6
+ class Method(
7
+ name: String,
8
+ argsType: Array<AnyType>,
9
+ private val body: (args: Array<out Any?>) -> Any?
10
+ ) : AnyMethod(name, argsType) {
11
+ override fun callImplementation(args: Array<out Any?>, promise: Promise) {
12
+ promise.resolve(body(args))
13
+ }
14
+ }