expo-modules-core 3.0.13 → 3.0.15

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 (29) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/cpp/ExpoModulesHostObject.cpp +4 -0
  4. package/android/src/main/java/expo/modules/kotlin/KClassExtensions.kt +3 -0
  5. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructorFactory.kt +3 -2
  6. package/android/src/main/java/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +4 -4
  7. package/android/src/main/java/expo/modules/kotlin/defaultmodules/CoreModule.kt +17 -12
  8. package/android/src/main/java/expo/modules/kotlin/defaultmodules/FilePermissionModule.kt +1 -1
  9. package/android/src/main/java/expo/modules/kotlin/devtools/OkHttpExtensions.kt +6 -2
  10. package/android/src/main/java/expo/modules/kotlin/events/KModuleEventEmitterWrapper.kt +4 -2
  11. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +0 -30
  12. package/android/src/main/java/expo/modules/kotlin/jni/JNIDeallocator.kt +20 -18
  13. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +1 -1
  14. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +3 -4
  15. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt +4 -0
  16. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectTypeConverter.kt +3 -1
  17. package/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedRef.kt +4 -0
  18. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +3 -7
  19. package/android/src/main/java/expo/modules/kotlin/types/Either.kt +1 -1
  20. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +25 -18
  21. package/android/src/main/java/expo/modules/kotlin/types/TypedArrayTypeConverter.kt +16 -37
  22. package/android/src/main/java/expo/modules/kotlin/types/io/FileTypeConverter.kt +2 -0
  23. package/android/src/main/java/expo/modules/kotlin/views/ExpoView.kt +1 -1
  24. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +13 -13
  25. package/android/src/main/java/expo/modules/kotlin/views/ViewTypeConverter.kt +1 -3
  26. package/ios/Core/Convertibles/Convertibles+Color.swift +8 -4
  27. package/ios/Core/Views/SwiftUI/Convertibles+SwiftUI.swift +5 -2
  28. package/ios/FileSystemUtilities/FileSystemUtilities.swift +3 -11
  29. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -10,6 +10,22 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 3.0.15 — 2025-09-10
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android] Fixes `JNI detected error in application: obj == null` in `ExpoModulesHostObject::get`. ([#39523](https://github.com/expo/expo/pull/39523) by [@lukmccall](https://github.com/lukmccall))
18
+
19
+ ## 3.0.14 — 2025-09-08
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - [Android] Fix type check in the `SharedRef` converter. ([#39446](https://github.com/expo/expo/pull/39446) by [@lukmccall](https://github.com/lukmccall))
24
+
25
+ ### 💡 Others
26
+
27
+ - improve startup performance by not relying on kotlin reflection. ([#39389](https://github.com/expo/expo/pull/39389) by [@ACHP](https://github.com/ACHP))
28
+
13
29
  ## 3.0.13 — 2025-09-04
14
30
 
15
31
  ### 💡 Others
@@ -21,6 +37,8 @@
21
37
  ### 💡 Others
22
38
 
23
39
  - Use new LongLivedObject.h and CallbackWrapper.h headers namespace ([#39344](https://github.com/expo/expo/pull/39344) by [@gabrieldonadel](https://github.com/gabrieldonadel))
40
+ - [iOS] Added `Color` convertible support without passing `AppContext`. ([#39183](https://github.com/expo/expo/pull/39183) by [@aleqsio](https://github.com/aleqsio))
41
+ - Extracted the read permission check from `FileSystemUtilities` ([#39210](https://github.com/expo/expo/pull/39210) by [@kosmydel](https://github.com/kosmydel))
24
42
 
25
43
  ## 3.0.11 — 2025-09-02
26
44
 
@@ -25,7 +25,7 @@ if (shouldIncludeCompose) {
25
25
  }
26
26
 
27
27
  group = 'host.exp.exponent'
28
- version = '3.0.13'
28
+ version = '3.0.15'
29
29
 
30
30
  def isExpoModulesCoreTests = {
31
31
  Gradle gradle = getGradle()
@@ -75,7 +75,7 @@ android {
75
75
  defaultConfig {
76
76
  consumerProguardFiles 'proguard-rules.pro'
77
77
  versionCode 1
78
- versionName "3.0.13"
78
+ versionName "3.0.15"
79
79
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
80
80
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
81
81
 
@@ -28,6 +28,10 @@ ExpoModulesHostObject::~ExpoModulesHostObject() {
28
28
  }
29
29
 
30
30
  jsi::Value ExpoModulesHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
31
+ if (installer->wasDeallocated()) {
32
+ return jsi::Value::undefined();
33
+ }
34
+
31
35
  auto cName = name.utf8(runtime);
32
36
 
33
37
  if (UniqueJSIObject &cachedObject = modulesCache[cName]) {
@@ -8,3 +8,6 @@ val <T : Any> KClass<T>.fastPrimaryConstructor: KFunction<T>?
8
8
  // If class only has one constructor, use it as a primary constructor.
9
9
  // Otherwise, try to find the primary constructor using kotlin reflection.
10
10
  get() = constructors.singleOrNull() ?: primaryConstructor
11
+
12
+ fun KClass<*>.fastIsSupperClassOf(jClass: Class<*>) =
13
+ this.javaObjectType.isAssignableFrom(jClass) || this.java.isAssignableFrom(jClass)
@@ -8,7 +8,8 @@ import kotlin.reflect.KParameter
8
8
  */
9
9
  class ObjectConstructorFactory {
10
10
  fun <T : Any> get(clazz: KClass<T>): ObjectConstructor<T> =
11
- tryToUseDefaultConstructor(clazz.java) ?: tryToUseDefaultKotlinConstructor(clazz)
11
+ tryToUseDefaultConstructor(clazz.java)
12
+ ?: tryToUseDefaultKotlinConstructor(clazz)
12
13
  ?: useUnsafeAllocator(clazz.java)
13
14
 
14
15
  private fun <T> tryToUseDefaultConstructor(clazz: Class<T>): ObjectConstructor<T>? {
@@ -21,7 +22,7 @@ class ObjectConstructorFactory {
21
22
  ObjectConstructor {
22
23
  ctor.newInstance() as T
23
24
  }
24
- } catch (e: NoSuchMethodException) {
25
+ } catch (_: NoSuchMethodException) {
25
26
  null
26
27
  }
27
28
  }
@@ -10,7 +10,8 @@ import expo.modules.kotlin.functions.SyncFunctionComponent
10
10
  import expo.modules.kotlin.objects.ObjectDefinitionBuilder
11
11
  import expo.modules.kotlin.objects.PropertyComponentBuilderWithThis
12
12
  import expo.modules.kotlin.sharedobjects.SharedObject
13
- import expo.modules.kotlin.sharedobjects.SharedRef
13
+ import expo.modules.kotlin.sharedobjects.isSharedObjectClass
14
+ import expo.modules.kotlin.sharedobjects.isSharedRefClass
14
15
  import expo.modules.kotlin.traits.Trait
15
16
  import expo.modules.kotlin.types.AnyType
16
17
  import expo.modules.kotlin.types.TypeConverterProvider
@@ -19,7 +20,6 @@ import expo.modules.kotlin.types.toAnyType
19
20
  import expo.modules.kotlin.types.toArgsArray
20
21
  import expo.modules.kotlin.types.toReturnType
21
22
  import kotlin.reflect.KClass
22
- import kotlin.reflect.full.isSubclassOf
23
23
 
24
24
  class ClassComponentBuilder<SharedObjectType : Any>(
25
25
  private val appContext: AppContext,
@@ -33,8 +33,8 @@ class ClassComponentBuilder<SharedObjectType : Any>(
33
33
 
34
34
  fun buildClass(): ClassDefinitionData {
35
35
  val hasOwnerType = ownerClass != Unit::class
36
- val isSharedObject = hasOwnerType && ownerClass.isSubclassOf(SharedObject::class)
37
- val isSharedRef = hasOwnerType && ownerClass.isSubclassOf(SharedRef::class)
36
+ val isSharedObject = hasOwnerType && ownerClass.isSharedObjectClass()
37
+ val isSharedRef = hasOwnerType && ownerClass.isSharedRefClass()
38
38
 
39
39
  if (eventsDefinition != null && isSharedObject) {
40
40
  listOf("__expo_onStartListeningToEvent" to SharedObject::onStartListeningToEvent, "__expo_onStopListeningToEvent" to SharedObject::onStopListeningToEvent)
@@ -19,16 +19,19 @@ class CoreModule : Module() {
19
19
 
20
20
  override fun definition() = ModuleDefinition {
21
21
  Property("expoModulesCoreVersion") {
22
- return@Property BuildConfig.EXPO_MODULES_CORE_VERSION.let { version ->
23
- version.split("-")[0].split(".").map { it.toInt() }.let { (major, minor, patch) ->
24
- mapOf(
25
- "version" to version,
26
- "major" to major,
27
- "minor" to minor,
28
- "patch" to patch
29
- )
30
- }
31
- }
22
+ val version = BuildConfig.EXPO_MODULES_CORE_VERSION
23
+ val (major, minor, patch) = version
24
+ .split("-")
25
+ .first()
26
+ .split(".")
27
+ .map { it.toInt() }
28
+
29
+ return@Property mapOf(
30
+ "version" to version,
31
+ "major" to major,
32
+ "minor" to minor,
33
+ "patch" to patch
34
+ )
32
35
  }
33
36
 
34
37
  Property("cacheDir") {
@@ -47,7 +50,7 @@ class CoreModule : Module() {
47
50
  Function("uuidv5") { name: String, namespace: String ->
48
51
  val namespaceUUID = try {
49
52
  UUID.fromString(namespace)
50
- } catch (e: IllegalArgumentException) {
53
+ } catch (_: IllegalArgumentException) {
51
54
  throw InvalidNamespaceException(namespace)
52
55
  }
53
56
  return@Function uuidv5(namespaceUUID, name).toString()
@@ -57,7 +60,9 @@ class CoreModule : Module() {
57
60
  val holder = runtimeContext.registry.getModuleHolder(moduleName)
58
61
  ?: return@Function null
59
62
 
60
- val viewManagerDefinition = holder.definition.viewManagerDefinitions[viewName ?: DEFAULT_MODULE_VIEW]
63
+ val viewManagerDefinition = holder
64
+ .definition
65
+ .viewManagerDefinitions[viewName ?: DEFAULT_MODULE_VIEW]
61
66
  ?: return@Function null
62
67
 
63
68
  val validAttributes = viewManagerDefinition
@@ -22,7 +22,7 @@ open class FilePermissionModule : FilePermissionModuleInterface, InternalModule
22
22
  getInternalPaths(context)
23
23
  .firstOrNull { dir -> canonicalPath.startsWith("$dir/") || dir == canonicalPath }
24
24
  ?.let { EnumSet.of(Permission.READ, Permission.WRITE) }
25
- } catch (e: IOException) {
25
+ } catch (_: IOException) {
26
26
  EnumSet.noneOf(Permission::class.java)
27
27
  }
28
28
  }
@@ -31,7 +31,7 @@ fun Headers.toSingleMap(): Map<String, String> {
31
31
  */
32
32
  suspend inline fun Request.await(okHttpClient: OkHttpClient): Response {
33
33
  return suspendCancellableCoroutine { callback ->
34
- okHttpClient.newCall(this).enqueue(object : Callback {
34
+ val responseCallback = object : Callback {
35
35
  override fun onResponse(call: Call, response: Response) {
36
36
  callback.resume(response)
37
37
  }
@@ -42,6 +42,10 @@ suspend inline fun Request.await(okHttpClient: OkHttpClient): Response {
42
42
  }
43
43
  callback.resumeWithException(e)
44
44
  }
45
- })
45
+ }
46
+
47
+ okHttpClient
48
+ .newCall(this)
49
+ .enqueue(responseCallback)
46
50
  }
47
51
  }
@@ -95,16 +95,18 @@ open class KEventEmitterWrapper(
95
95
 
96
96
  override fun emit(viewId: Int, eventName: String, eventBody: WritableMap?, coalescingKey: Short?) {
97
97
  val context = reactContextHolder.get() ?: return
98
+ val uiEvent = UIEvent(surfaceId = -1, viewId, eventName, eventBody, coalescingKey)
98
99
  UIManagerHelper.getEventDispatcherForReactTag(context, viewId)
99
- ?.dispatchEvent(UIEvent(surfaceId = -1, viewId, eventName, eventBody, coalescingKey))
100
+ ?.dispatchEvent(uiEvent)
100
101
  }
101
102
 
102
103
  override fun emit(view: View, eventName: String, eventBody: WritableMap?, coalescingKey: Short?) {
103
104
  val context = reactContextHolder.get() ?: return
104
105
  val surfaceId = UIManagerHelper.getSurfaceId(view)
105
106
  val viewId = view.id
107
+ val uiEvent = UIEvent(surfaceId, viewId, eventName, eventBody, coalescingKey)
106
108
  UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
107
- ?.dispatchEvent(UIEvent(surfaceId, viewId, eventName, eventBody, coalescingKey))
109
+ ?.dispatchEvent(uiEvent)
108
110
  }
109
111
 
110
112
  private class UIEvent(
@@ -1,16 +1,13 @@
1
1
  package expo.modules.kotlin.functions
2
2
 
3
- import com.facebook.react.bridge.ReadableArray
4
3
  import expo.modules.kotlin.AppContext
5
4
  import expo.modules.kotlin.exception.ArgumentCastException
6
5
  import expo.modules.kotlin.exception.CodedException
7
6
  import expo.modules.kotlin.exception.InvalidArgsNumberException
8
7
  import expo.modules.kotlin.exception.exceptionDecorator
9
- import expo.modules.kotlin.iterator
10
8
  import expo.modules.kotlin.jni.ExpectedType
11
9
  import expo.modules.kotlin.jni.JavaScriptObject
12
10
  import expo.modules.kotlin.jni.decorators.JSDecoratorsBridgingObject
13
- import expo.modules.kotlin.recycle
14
11
  import expo.modules.kotlin.types.AnyType
15
12
  import kotlin.reflect.KClass
16
13
  import kotlin.reflect.KType
@@ -61,33 +58,6 @@ abstract class AnyFunction(
61
58
  return@run desiredArgsTypes.size - nonNullableArgIndex
62
59
  }
63
60
 
64
- /**
65
- * Tries to convert arguments from RN representation to expected types.
66
- *
67
- * @return An array of converted arguments
68
- * @throws `CodedException` if conversion isn't possible
69
- */
70
- @Throws(CodedException::class)
71
- protected fun convertArgs(args: ReadableArray): Array<out Any?> {
72
- if (requiredArgumentsCount > args.size() || args.size() > desiredArgsTypes.size) {
73
- throw InvalidArgsNumberException(args.size(), desiredArgsTypes.size, requiredArgumentsCount)
74
- }
75
-
76
- val finalArgs = arrayOfNulls<Any?>(desiredArgsTypes.size)
77
- val argIterator = args.iterator()
78
- for (index in 0 until args.size()) {
79
- val desiredType = desiredArgsTypes[index]
80
- argIterator.next().recycle {
81
- exceptionDecorator({ cause ->
82
- ArgumentCastException(desiredType.kType, index, type.toString(), cause)
83
- }) {
84
- finalArgs[index] = desiredType.convert(this)
85
- }
86
- }
87
- }
88
- return finalArgs
89
- }
90
-
91
61
  /**
92
62
  * Tries to convert arguments from [Any]? to expected types.
93
63
  *
@@ -12,6 +12,10 @@ interface Destructible {
12
12
 
13
13
  @DoNotStrip
14
14
  class JNIDeallocator(shouldCreateDestructorThread: Boolean = true) : AutoCloseable {
15
+ private inner class DeallocatorThread : Thread("Expo JNI deallocator") {
16
+ override fun run() = deallocator()
17
+ }
18
+
15
19
  /**
16
20
  * A [PhantomReference] queue managed by JVM
17
21
  */
@@ -27,24 +31,7 @@ class JNIDeallocator(shouldCreateDestructorThread: Boolean = true) : AutoCloseab
27
31
  * to not store invalid references to every created object.
28
32
  */
29
33
  private val destructorThread = if (shouldCreateDestructorThread) {
30
- object : Thread("Expo JNI deallocator") {
31
- override fun run() {
32
- while (!isInterrupted) {
33
- try {
34
- // Referent of PhantomReference were garbage collected so we can remove it from our registry.
35
- // Note that we don't have to call `deallocate` method - it was called [com.facebook.jni.HybridData].
36
- val current = referenceQueue.remove()
37
- synchronized(this@JNIDeallocator) {
38
- destructorMap.remove(current)
39
- }
40
- } catch (e: InterruptedException) {
41
- return
42
- }
43
- }
44
- }
45
- }.also {
46
- it.start()
47
- }
34
+ DeallocatorThread().apply { start() }
48
35
  } else {
49
36
  null
50
37
  }
@@ -80,6 +67,21 @@ class JNIDeallocator(shouldCreateDestructorThread: Boolean = true) : AutoCloseab
80
67
  destructorMap.values.mapNotNull { it.get() }
81
68
  }
82
69
 
70
+ private fun Thread.deallocator() {
71
+ while (!isInterrupted) {
72
+ try {
73
+ // Referent of PhantomReference were garbage collected so we can remove it from our registry.
74
+ // Note that we don't have to call `deallocate` method - it was called [com.facebook.jni.HybridData].
75
+ val current = referenceQueue.remove()
76
+ synchronized(this@JNIDeallocator) {
77
+ destructorMap.remove(current)
78
+ }
79
+ } catch (_: InterruptedException) {
80
+ return
81
+ }
82
+ }
83
+ }
84
+
83
85
  override fun close() {
84
86
  deallocate()
85
87
  }
@@ -30,7 +30,7 @@ class JavaScriptTypedArray @DoNotStrip constructor(hybridData: HybridData) :
30
30
 
31
31
  override val kind: TypedArrayKind by lazy {
32
32
  val rawKind = getRawKind()
33
- TypedArrayKind.values().first { it.value == rawKind }
33
+ TypedArrayKind.entries.first { it.value == rawKind }
34
34
  }
35
35
 
36
36
  override val length: Int by lazy {
@@ -32,18 +32,17 @@ class RecordTypeConverter<T : Record>(
32
32
  private val propertyDescriptors: Map<KProperty1<out Any, *>, PropertyDescriptor> by lazy {
33
33
  (type.classifier as KClass<*>)
34
34
  .memberProperties
35
- .map { property ->
36
- val fieldAnnotation = property.findAnnotation<Field>() ?: return@map null
35
+ .mapNotNull { property ->
36
+ val fieldAnnotation = property.findAnnotation<Field>() ?: return@mapNotNull null
37
37
  val typeConverter = converterProvider.obtainTypeConverter(property.returnType)
38
38
 
39
- return@map property to PropertyDescriptor(
39
+ return@mapNotNull property to PropertyDescriptor(
40
40
  typeConverter,
41
41
  fieldAnnotation,
42
42
  isRequired = property.findAnnotation<Required>() != null,
43
43
  validators = getValidators(property)
44
44
  )
45
45
  }
46
- .filterNotNull()
47
46
  .toMap()
48
47
  }
49
48
 
@@ -8,6 +8,7 @@ import expo.modules.kotlin.jni.JavaScriptWeakObject
8
8
  import expo.modules.kotlin.logger
9
9
  import expo.modules.kotlin.types.JSTypeConverter
10
10
  import expo.modules.kotlin.weak
11
+ import kotlin.reflect.KClass
11
12
 
12
13
  @DoNotStrip
13
14
  open class SharedObject(runtimeContext: RuntimeContext? = null) {
@@ -83,3 +84,6 @@ open class SharedObject(runtimeContext: RuntimeContext? = null) {
83
84
  return 0
84
85
  }
85
86
  }
87
+
88
+ fun KClass<*>.isSharedObjectClass() =
89
+ SharedObject::class.java.isAssignableFrom(this.java)
@@ -3,6 +3,7 @@ package expo.modules.kotlin.sharedobjects
3
3
  import com.facebook.react.bridge.Dynamic
4
4
  import expo.modules.kotlin.AppContext
5
5
  import expo.modules.kotlin.exception.IncorrectRefTypeException
6
+ import expo.modules.kotlin.fastIsSupperClassOf
6
7
  import expo.modules.kotlin.jni.CppType
7
8
  import expo.modules.kotlin.jni.ExpectedType
8
9
  import expo.modules.kotlin.toStrongReference
@@ -73,7 +74,8 @@ class SharedRefTypeConverter<T : SharedRef<*>>(
73
74
  val ref = sharedRef.ref ?: return sharedRef
74
75
  val sharedRefClass = sharedRefType?.classifier as? KClass<*>
75
76
  ?: return sharedRef
76
- if (ref::class.java.isAssignableFrom(sharedRefClass.javaObjectType) || ref::class.java.isAssignableFrom(sharedRefClass.java)) {
77
+
78
+ if (sharedRefClass.fastIsSupperClassOf(ref.javaClass)) {
77
79
  return sharedRef
78
80
  }
79
81
 
@@ -3,6 +3,7 @@ package expo.modules.kotlin.sharedobjects
3
3
  import expo.modules.core.interfaces.DoNotStrip
4
4
  import expo.modules.kotlin.AppContext
5
5
  import expo.modules.kotlin.RuntimeContext
6
+ import kotlin.reflect.KClass
6
7
 
7
8
  /**
8
9
  * Shared object (ref) that holds a strong reference to any native object. Allows passing references
@@ -29,3 +30,6 @@ inline fun <reified RefType> SharedRef<*>.cast(): SharedRef<RefType>? {
29
30
 
30
31
  return null
31
32
  }
33
+
34
+ fun KClass<*>.isSharedRefClass() =
35
+ SharedRef::class.java.isAssignableFrom(this.java)
@@ -21,7 +21,6 @@ import expo.modules.kotlin.typedarray.Uint16Array
21
21
  import expo.modules.kotlin.typedarray.Uint32Array
22
22
  import expo.modules.kotlin.typedarray.Uint8Array
23
23
  import expo.modules.kotlin.typedarray.Uint8ClampedArray
24
- import expo.modules.kotlin.types.AnyTypeProvider.typesMap
25
24
  import java.io.File
26
25
  import java.net.URI
27
26
  import java.net.URL
@@ -90,7 +89,7 @@ class EmptyKType(
90
89
 
91
90
  object AnyTypeProvider {
92
91
  @PublishedApi
93
- internal val typesMap = buildMap<Pair<KClass<*>, Boolean>, AnyType> {
92
+ internal val typesMap = buildMap {
94
93
  listOf(
95
94
  Int::class,
96
95
  Float::class,
@@ -312,11 +311,8 @@ class AnyType(
312
311
  ) {
313
312
 
314
313
  private val converter: TypeConverter<*> by lazy {
315
- if (converterProvider != null) {
316
- converterProvider.obtainTypeConverter(kType)
317
- } else {
318
- TypeConverterProviderImpl.obtainTypeConverter(kType)
319
- }
314
+ converterProvider?.obtainTypeConverter(kType)
315
+ ?: TypeConverterProviderImpl.obtainTypeConverter(kType)
320
316
  }
321
317
 
322
318
  fun convert(value: Any?, appContext: AppContext? = null, forceConversion: Boolean = false): Any? {
@@ -60,7 +60,7 @@ open class Either<FirstType : Any, SecondType : Any>(
60
60
  try {
61
61
  deferredValue[index] = ConvertedValue(value.getConvertedValue())
62
62
  true
63
- } catch (e: Throwable) {
63
+ } catch (_: Throwable) {
64
64
  deferredValue[index] = IncompatibleValue
65
65
  false
66
66
  }
@@ -40,7 +40,10 @@ class EnumTypeConverter(
40
40
 
41
41
  override fun convertFromDynamic(value: Dynamic, context: AppContext?, forceConversion: Boolean): Enum<*> {
42
42
  if (primaryConstructor.parameters.isEmpty()) {
43
- return convertEnumWithoutParameter(value.asString() ?: throw DynamicCastException(String::class), enumConstants)
43
+ return convertEnumWithoutParameter(
44
+ value.asString() ?: throw DynamicCastException(String::class),
45
+ enumConstants
46
+ )
44
47
  } else if (primaryConstructor.parameters.size == 1) {
45
48
  return convertEnumWithParameter(
46
49
  value,
@@ -93,23 +96,7 @@ class EnumTypeConverter(
93
96
  filed.isAccessible = true
94
97
 
95
98
  val parameterType = filed.type
96
- val jsUnwrapValue = if (jsValue is Dynamic) {
97
- if (parameterType == String::class.java) {
98
- jsValue.asString()
99
- } else {
100
- jsValue.asInt()
101
- }
102
- } else {
103
- if (parameterType == String::class.java) {
104
- jsValue as String
105
- } else {
106
- if (jsValue is Double) {
107
- jsValue.toInt()
108
- } else {
109
- jsValue as Int
110
- }
111
- }
112
- }
99
+ val jsUnwrapValue = jsValue.unwrapValue(parameterType)
113
100
 
114
101
  return requireNotNull(
115
102
  enumConstants.find {
@@ -117,4 +104,24 @@ class EnumTypeConverter(
117
104
  }
118
105
  ) { "Couldn't convert '$jsValue' to ${enumClass.simpleName} where $parameterName is the enum parameter" }
119
106
  }
107
+
108
+ private fun Any.unwrapValue(parameterType: Class<*>): Any? {
109
+ if (this is Dynamic) {
110
+ if (parameterType == String::class.java) {
111
+ return asString()
112
+ }
113
+
114
+ return asInt()
115
+ }
116
+
117
+ if (parameterType == String::class.java) {
118
+ return this as String
119
+ }
120
+
121
+ if (this is Double) {
122
+ return toInt()
123
+ }
124
+
125
+ return this as Int
126
+ }
120
127
  }
@@ -17,59 +17,38 @@ import expo.modules.kotlin.typedarray.Uint32Array
17
17
  import expo.modules.kotlin.typedarray.Uint8Array
18
18
  import expo.modules.kotlin.typedarray.Uint8ClampedArray
19
19
 
20
- abstract class BaseTypeArrayConverter<T : TypedArray>() : NonNullableTypeConverter<T>() {
20
+ open class BaseTypeArrayConverter<T : TypedArray>(
21
+ val typedArrayWrapper: (rawArray: JavaScriptTypedArray) -> T
22
+ ) : NonNullableTypeConverter<T>() {
21
23
  override fun convertNonNullable(value: Any, context: AppContext?, forceConversion: Boolean): T = wrapJavaScriptTypedArray(value as JavaScriptTypedArray)
22
24
  override fun getCppRequiredTypes(): ExpectedType = ExpectedType(CppType.TYPED_ARRAY)
23
25
  override fun isTrivial() = false
24
26
 
25
- abstract fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray): T
27
+ fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray): T = typedArrayWrapper(value)
26
28
  }
27
29
 
28
- class Int8ArrayTypeConverter() : BaseTypeArrayConverter<Int8Array>() {
29
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Int8Array(value)
30
- }
30
+ class Int8ArrayTypeConverter() : BaseTypeArrayConverter<Int8Array>(::Int8Array)
31
31
 
32
- class Int16ArrayTypeConverter() : BaseTypeArrayConverter<Int16Array>() {
33
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Int16Array(value)
34
- }
32
+ class Int16ArrayTypeConverter() : BaseTypeArrayConverter<Int16Array>(::Int16Array)
35
33
 
36
- class Int32ArrayTypeConverter() : BaseTypeArrayConverter<Int32Array>() {
37
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Int32Array(value)
38
- }
34
+ class Int32ArrayTypeConverter() : BaseTypeArrayConverter<Int32Array>(::Int32Array)
39
35
 
40
- class Uint8ArrayTypeConverter() : BaseTypeArrayConverter<Uint8Array>() {
41
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Uint8Array(value)
42
- }
36
+ class Uint8ArrayTypeConverter() : BaseTypeArrayConverter<Uint8Array>(::Uint8Array)
43
37
 
44
- class Uint8ClampedArrayTypeConverter() : BaseTypeArrayConverter<Uint8ClampedArray>() {
45
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Uint8ClampedArray(value)
46
- }
38
+ class Uint8ClampedArrayTypeConverter() : BaseTypeArrayConverter<Uint8ClampedArray>(::Uint8ClampedArray)
47
39
 
48
- class Uint16ArrayTypeConverter() : BaseTypeArrayConverter<Uint16Array>() {
49
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Uint16Array(value)
50
- }
40
+ class Uint16ArrayTypeConverter() : BaseTypeArrayConverter<Uint16Array>(::Uint16Array)
51
41
 
52
- class Uint32ArrayTypeConverter() : BaseTypeArrayConverter<Uint32Array>() {
53
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Uint32Array(value)
54
- }
42
+ class Uint32ArrayTypeConverter() : BaseTypeArrayConverter<Uint32Array>(::Uint32Array)
55
43
 
56
- class Float32ArrayTypeConverter() : BaseTypeArrayConverter<Float32Array>() {
57
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Float32Array(value)
58
- }
44
+ class Float32ArrayTypeConverter() : BaseTypeArrayConverter<Float32Array>(::Float32Array)
59
45
 
60
- class Float64ArrayTypeConverter() : BaseTypeArrayConverter<Float64Array>() {
61
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = Float64Array(value)
62
- }
46
+ class Float64ArrayTypeConverter() : BaseTypeArrayConverter<Float64Array>(::Float64Array)
63
47
 
64
- class BigInt64ArrayTypeConverter() : BaseTypeArrayConverter<BigInt64Array>() {
65
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = BigInt64Array(value)
66
- }
48
+ class BigInt64ArrayTypeConverter() : BaseTypeArrayConverter<BigInt64Array>(::BigInt64Array)
67
49
 
68
- class BigUint64ArrayTypeConverter() : BaseTypeArrayConverter<BigUint64Array>() {
69
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray) = BigUint64Array(value)
70
- }
50
+ class BigUint64ArrayTypeConverter() : BaseTypeArrayConverter<BigUint64Array>(::BigUint64Array)
71
51
 
72
- class TypedArrayTypeConverter() : BaseTypeArrayConverter<TypedArray>() {
73
- override fun wrapJavaScriptTypedArray(value: JavaScriptTypedArray): TypedArray = value
52
+ class TypedArrayTypeConverter() : BaseTypeArrayConverter<TypedArray>({ it }) {
74
53
  override fun isTrivial() = true
75
54
  }
@@ -2,6 +2,7 @@ package expo.modules.kotlin.types.io
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
4
  import expo.modules.kotlin.AppContext
5
+ import expo.modules.kotlin.exception.Exceptions
5
6
  import expo.modules.kotlin.jni.CppType
6
7
  import expo.modules.kotlin.jni.ExpectedType
7
8
  import expo.modules.kotlin.types.DynamicAwareTypeConverters
@@ -10,6 +11,7 @@ import java.io.File
10
11
  class FileTypeConverter : DynamicAwareTypeConverters<File>() {
11
12
  override fun convertFromDynamic(value: Dynamic, context: AppContext?, forceConversion: Boolean): File {
12
13
  val path = value.asString()
14
+ ?: throw Exceptions.IllegalArgument("Cannot convert ${value.type} to File")
13
15
  return File(path)
14
16
  }
15
17
 
@@ -50,7 +50,7 @@ abstract class ExpoView(
50
50
  super.requestLayout()
51
51
  if (shouldUseAndroidLayout) {
52
52
  // We need to force measure and layout, because React Native doesn't do it for us.
53
- post(Runnable { measureAndLayout() })
53
+ post { measureAndLayout() }
54
54
  }
55
55
  }
56
56
 
@@ -28,7 +28,9 @@ class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder<*>, int
28
28
  fun onViewDidUpdateProps(view: View) {
29
29
  definition.onViewDidUpdateProps?.let {
30
30
  try {
31
- exceptionDecorator({ OnViewDidUpdatePropsException(view.javaClass.kotlin, it) }) {
31
+ exceptionDecorator(
32
+ { exception -> OnViewDidUpdatePropsException(view.javaClass.kotlin, exception) }
33
+ ) {
32
34
  it.invoke(view)
33
35
  }
34
36
  } catch (exception: Throwable) {
@@ -103,17 +105,15 @@ class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder<*>, int
103
105
  }
104
106
  }
105
107
 
106
- fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
107
- return buildMap<String, Any> {
108
- definition
109
- .callbacksDefinition
110
- ?.names
111
- ?.forEach {
112
- put(
113
- normalizeEventName(it),
114
- mapOf("registrationName" to it)
115
- )
116
- }
117
- }
108
+ fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? = buildMap {
109
+ definition
110
+ .callbacksDefinition
111
+ ?.names
112
+ ?.forEach {
113
+ put(
114
+ normalizeEventName(it),
115
+ mapOf("registrationName" to it)
116
+ )
117
+ }
118
118
  }
119
119
  }
@@ -19,9 +19,7 @@ class ViewTypeConverter<T : View>(
19
19
 
20
20
  val viewTag = value as Int
21
21
  val view = appContext.findView<T>(viewTag)
22
- if (view == null) {
23
- throw Exceptions.ViewNotFound(type.classifier as KClass<*>, viewTag)
24
- }
22
+ ?: throw Exceptions.ViewNotFound(type.classifier as KClass<*>, viewTag)
25
23
 
26
24
  return view
27
25
  }
@@ -2,6 +2,10 @@
2
2
 
3
3
  extension UIColor: Convertible {
4
4
  public static func convert(from value: Any?, appContext: AppContext) throws -> Self {
5
+ return try convert(from: value)
6
+ }
7
+
8
+ public static func convert(from value: Any?) throws -> Self {
5
9
  // swiftlint:disable force_cast
6
10
  if let value = value as? String {
7
11
  if let namedColorComponents = namedColors[value] {
@@ -30,10 +34,10 @@ extension UIColor: Convertible {
30
34
  }
31
35
  }
32
36
  if let appearances = opaqueValue["dynamic"] as? [String: Any],
33
- let lightColor = try appearances["light"].map({ try UIColor.convert(from: $0, appContext: appContext) }),
34
- let darkColor = try appearances["dark"].map({ try UIColor.convert(from: $0, appContext: appContext) }) {
35
- let highContrastLightColor = try appearances["highContrastLight"].map({ try UIColor.convert(from: $0, appContext: appContext) })
36
- let highContrastDarkColor = try appearances["highContrastDark"].map({ try UIColor.convert(from: $0, appContext: appContext) })
37
+ let lightColor = try appearances["light"].map({ try UIColor.convert(from: $0) }),
38
+ let darkColor = try appearances["dark"].map({ try UIColor.convert(from: $0) }) {
39
+ let highContrastLightColor = try appearances["highContrastLight"].map({ try UIColor.convert(from: $0) })
40
+ let highContrastDarkColor = try appearances["highContrastDark"].map({ try UIColor.convert(from: $0) })
37
41
 
38
42
  #if os(iOS) || os(tvOS)
39
43
  let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
@@ -3,9 +3,9 @@
3
3
  import SwiftUI
4
4
 
5
5
  extension Color: Convertible {
6
- public static func convert(from value: Any?, appContext: AppContext) throws -> Color {
6
+ public static func convert(from value: Any?) throws -> Color {
7
7
  // Simply reuse the logic from UIColor
8
- if let uiColor = try? UIColor.convert(from: value, appContext: appContext) {
8
+ if let uiColor = try? UIColor.convert(from: value) {
9
9
  return Color(uiColor)
10
10
  }
11
11
  // Context-dependent colors
@@ -14,6 +14,9 @@ extension Color: Convertible {
14
14
  }
15
15
  throw Conversions.ConvertingException<Color>(value)
16
16
  }
17
+ public static func convert(from value: Any?, appContext: AppContext) throws -> Color {
18
+ return try convert(from: value)
19
+ }
17
20
 
18
21
  private static func colorFromName(_ name: String) -> Color? {
19
22
  switch name {
@@ -59,7 +59,7 @@ public struct FileSystemUtilities {
59
59
  return getExternalPathPermissions(path, appContext)
60
60
  }
61
61
 
62
- private static func getInternalPathPermissions(_ appContext: AppContext?, for url: URL) -> [FileSystemPermissionFlags] {
62
+ public static func getInternalPathPermissions(_ appContext: AppContext?, for url: URL) -> [FileSystemPermissionFlags] {
63
63
  guard let appContext else {
64
64
  return [.none]
65
65
  }
@@ -88,17 +88,9 @@ public struct FileSystemUtilities {
88
88
  if appContext?.config.scoped ?? false && url.path.contains("ExponentExperienceData") {
89
89
  return []
90
90
  }
91
- var filePermissions: [FileSystemPermissionFlags] = []
92
91
 
93
- if FileManager.default.isReadableFile(atPath: url.path) {
94
- filePermissions.append(.read)
95
- }
96
-
97
- if FileManager.default.isWritableFile(atPath: url.path) {
98
- filePermissions.append(.write)
99
- }
100
-
101
- return filePermissions
92
+ // Defer permission checks for external paths to the underlying system at the time of file operations
93
+ return [.read, .write]
102
94
  }
103
95
 
104
96
  private static func getAppGroupSharedDirectories(_ appContext: AppContext) -> [String] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "3.0.13",
3
+ "version": "3.0.15",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@testing-library/react-native": "^13.2.0",
66
- "expo-module-scripts": "^5.0.6"
66
+ "expo-module-scripts": "^5.0.7"
67
67
  },
68
- "gitHead": "8cafaff8076e443e6c80e8013ec809f4f290f24d"
68
+ "gitHead": "87186d10c8239c6469e055417e67bd4d0ed63efb"
69
69
  }