expo-modules-core 3.0.23 → 3.0.24

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 3.0.24 — 2025-11-03
14
+
15
+ ### 🎉 New features
16
+
17
+ - [iOS] Added `subscriberDidRegister` function to AppDelegate subscribers. ([#40684](https://github.com/expo/expo/pull/40684) by [@tsapeta](https://github.com/tsapeta))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [iOS] Fix `Either` conversion in `Record` ([#40655](https://github.com/expo/expo/pull/40655) by [@vonovak](https://github.com/vonovak))
22
+
13
23
  ## 3.0.23 — 2025-10-28
14
24
 
15
25
  ### 🎉 New features
@@ -25,6 +35,7 @@
25
35
  ### 💡 Others
26
36
 
27
37
  - [iOS] Removed some runtime type checks for dynamic types. ([#40611](https://github.com/expo/expo/pull/40611) by [@tsapeta](https://github.com/tsapeta))
38
+ - [iOS] Added base props support for SwiftUI integration. ([#40492](https://github.com/expo/expo/pull/40492) by [@kudo](https://github.com/kudo))
28
39
 
29
40
  ## 3.0.22 — 2025-10-20
30
41
 
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
29
29
  }
30
30
 
31
31
  group = 'host.exp.exponent'
32
- version = '3.0.23'
32
+ version = '3.0.24'
33
33
 
34
34
  def isExpoModulesCoreTests = {
35
35
  Gradle gradle = getGradle()
@@ -79,7 +79,7 @@ android {
79
79
  defaultConfig {
80
80
  consumerProguardFiles 'proguard-rules.pro'
81
81
  versionCode 1
82
- versionName "3.0.23"
82
+ versionName "3.0.24"
83
83
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
84
84
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
85
85
 
@@ -15,7 +15,7 @@ public struct ViewDefinitionBuilder<ViewType: UIView> {
15
15
  }
16
16
 
17
17
  /**
18
- Accepts `Events` definition element of `View`.
18
+ Accepts `ViewName` definition element of `View`.
19
19
  */
20
20
  public static func buildExpression(_ element: ViewNameDefinition) -> AnyViewDefinitionElement {
21
21
  return element
@@ -24,6 +24,14 @@ open class BaseExpoAppDelegateSubscriber: UIResponder {
24
24
  @objc(EXAppDelegateSubscriberProtocol)
25
25
  public protocol ExpoAppDelegateSubscriberProtocol: UIApplicationDelegate {
26
26
  @objc optional func customizeRootView(_ rootView: UIView)
27
+
28
+ /**
29
+ Function that is called automatically by the `ExpoAppDelegate` once the subscriber is successfully registered.
30
+ If the subscriber is loaded from the modules provider, it is executed just before the main code of the app,
31
+ thus even before any other `UIApplicationDelegate` function. Use it if your subscriber needs to run some code as early as possible,
32
+ but keep in mind that this affects the application loading time.
33
+ */
34
+ @objc optional func subscriberDidRegister()
27
35
  }
28
36
 
29
37
  /**
@@ -32,6 +32,7 @@ public class ExpoAppDelegateSubscriberRepository: NSObject {
32
32
  fatalError("Given app delegate subscriber `\(String(describing: subscriber))` is already registered.")
33
33
  }
34
34
  _subscribers.append(subscriber)
35
+ subscriber.subscriberDidRegister?()
35
36
  }
36
37
 
37
38
  @objc
@@ -15,6 +15,11 @@ protocol AnyEither: AnyArgument {
15
15
  A dynamic either type for the current either type.
16
16
  */
17
17
  static func getDynamicType() -> any AnyDynamicType
18
+
19
+ /**
20
+ The underlying type-erased value.
21
+ */
22
+ var value: Any? { get }
18
23
  }
19
24
 
20
25
  /*
@@ -40,6 +40,23 @@ internal struct DynamicEitherType<EitherType: AnyEither>: AnyDynamicType {
40
40
  throw NeitherTypeException(types)
41
41
  }
42
42
 
43
+ func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
44
+ guard let either = result as? EitherType else {
45
+ throw Conversions.CastingException<EitherType>(result)
46
+ }
47
+
48
+ let types = eitherType.dynamicTypes()
49
+
50
+ // Try each type - one should succeed
51
+ for type in types {
52
+ if let converted = try? type.convertResult(either.value, appContext: appContext) {
53
+ return converted
54
+ }
55
+ }
56
+
57
+ throw NeitherTypeException(types)
58
+ }
59
+
43
60
  var description: String {
44
61
  let types = eitherType.dynamicTypes()
45
62
  return "Either<\(types.map(\.description).joined(separator: ", "))>"
@@ -68,12 +68,25 @@ public extension Record {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ Recursively collects all children from a Mirror, including inherited properties from superclasses.
73
+ */
74
+ internal func allMirrorChildren(_ mirror: Mirror) -> [Mirror.Child] {
75
+ var children: [Mirror.Child] = Array(mirror.children)
76
+ if let superclassMirror = mirror.superclassMirror {
77
+ children.append(contentsOf: allMirrorChildren(superclassMirror))
78
+ }
79
+ return children
80
+ }
81
+
71
82
  /**
72
83
  Returns an array of fields found in record's mirror. If the field is missing the `key`,
73
84
  it gets assigned to the property label, so after all it's safe to enforce unwrapping it (using `key!`).
85
+ This function now supports inheritance by recursively traversing the superclass hierarchy.
74
86
  */
75
87
  internal func fieldsOf(_ record: Record) -> [AnyFieldInternal] {
76
- return Mirror(reflecting: record).children.compactMap { (label: String?, value: Any) in
88
+ let mirror = Mirror(reflecting: record)
89
+ return allMirrorChildren(mirror).compactMap { (label: String?, value: Any) in
77
90
  guard var field = value as? AnyFieldInternal, let key = field.key ?? convertLabelToKey(label) else {
78
91
  return nil
79
92
  }
@@ -113,7 +113,7 @@ extension ExpoSwiftUI {
113
113
  }
114
114
 
115
115
  public override func getSupportedPropNames() -> [String] {
116
- return dummyPropsMirror.children.compactMap { (label: String?, value: Any) in
116
+ return allMirrorChildren(dummyPropsMirror).compactMap { (label: String?, value: Any) in
117
117
  guard let field = value as? AnyFieldInternal else {
118
118
  return nil
119
119
  }
@@ -122,14 +122,13 @@ extension ExpoSwiftUI {
122
122
  }
123
123
 
124
124
  public override func getSupportedEventNames() -> [String] {
125
- let builtInEventNames = [GLOBAL_EVENT_NAME]
126
- let propEventNames: [String] = dummyPropsMirror.children.compactMap { (label: String?, value: Any) in
125
+ let propEventNames: [String] = allMirrorChildren(dummyPropsMirror).compactMap { (label: String?, value: Any) in
127
126
  guard let event = value as? EventDispatcher else {
128
127
  return nil
129
128
  }
130
129
  return event.customName ?? convertLabelToKey(label)
131
130
  }
132
- return builtInEventNames + propEventNames
131
+ return propEventNames
133
132
  }
134
133
  }
135
134
  }
@@ -10,6 +10,13 @@ extension ExpoSwiftUI {
10
10
  return elements
11
11
  }
12
12
 
13
+ /**
14
+ Accepts `ViewName` definition element of `View`.
15
+ */
16
+ public static func buildExpression(_ element: ViewNameDefinition) -> AnyViewDefinitionElement {
17
+ return element
18
+ }
19
+
13
20
  /**
14
21
  Accepts functions as a view definition elements.
15
22
  */
@@ -15,7 +15,7 @@ extension ExpoSwiftUI {
15
15
  /**
16
16
  An array of views passed by React as children.
17
17
  */
18
- @Field public var children: [any AnyChild]?
18
+ public var children: [any AnyChild]?
19
19
 
20
20
  public internal(set) weak var appContext: AppContext?
21
21
 
@@ -37,7 +37,8 @@ extension ExpoSwiftUI {
37
37
  dispatcher(GLOBAL_EVENT_NAME, payload)
38
38
  }
39
39
 
40
- Mirror(reflecting: self).children.forEach { (label: String?, value: Any) in
40
+ let mirror = Mirror(reflecting: self)
41
+ allMirrorChildren(mirror).forEach { (label: String?, value: Any) in
41
42
  guard let event = value as? EventDispatcher else {
42
43
  return
43
44
  }
@@ -334,6 +334,10 @@ class FunctionSpec: ExpoSpec {
334
334
  p.resolve(SharedString("Test with Promise"))
335
335
  }
336
336
 
337
+ Function("withEither") { (either: Either<Bool, String>) in
338
+ return either
339
+ }
340
+
337
341
  AsyncFunction("withURLAsync") {
338
342
  return TestURLRecord.defaultURL
339
343
  }
@@ -504,7 +508,17 @@ class FunctionSpec: ExpoSpec {
504
508
  expect(result.kind) == .string
505
509
  expect(result.getString()) == "Test with Promise"
506
510
  }
507
-
511
+
512
+ it("accepts and returns Either value") {
513
+ let stringResult = try runtime.eval("expo.modules.TestModule.withEither('test string')")
514
+ expect(stringResult.kind) == .string
515
+ expect(stringResult.getString()) == "test string"
516
+
517
+ let boolResult = try runtime.eval("expo.modules.TestModule.withEither(true)")
518
+ expect(boolResult.kind) == .bool
519
+ expect(boolResult.getBool()) == true
520
+ }
521
+
508
522
  // For async tests, this is a safe way to repeatedly evaluate JS
509
523
  // and catch both Swift and ObjC exceptions
510
524
  func safeBoolEval(_ js: String) -> Bool {
@@ -45,6 +45,31 @@ class RecordSpec: ExpoSpec {
45
45
  expect(record.toDictionary()["b"] as? Int).to(equal(dict["b"]! as! Int))
46
46
  }
47
47
 
48
+ it("works back and forth with Either") {
49
+ struct TestRecord: Record {
50
+ @Field var stringValue: Either<Bool, String>?
51
+ @Field var boolValue: Either<Bool, String>?
52
+ @Field var intValue: Either<Int, String>?
53
+ @Field var nilValue: Either<Int, String>?
54
+ }
55
+ let dict: [String: Any] = [
56
+ "stringValue": "custom",
57
+ "boolValue": true,
58
+ "intValue": 42,
59
+ ]
60
+ let record = try TestRecord(from: dict, appContext: appContext)
61
+ expect(record.stringValue?.get() as String?).to(equal("custom"))
62
+ expect(record.boolValue?.get() as Bool?).to(equal(true))
63
+ expect(record.intValue?.get() as Int?).to(equal(42))
64
+ expect(record.nilValue).to(beNil())
65
+
66
+ let asDict = record.toDictionary(appContext: appContext)
67
+ expect(asDict["stringValue"] as? String).to(equal("custom"))
68
+ expect(asDict["boolValue"] as? Bool).to(equal(true))
69
+ expect(asDict["intValue"] as? Int).to(equal(42))
70
+ expect(asDict["nilValue"] as? Int).to(beNil())
71
+ }
72
+
48
73
  it("works back and forth with a keyed field") {
49
74
  struct TestRecord: Record {
50
75
  @Field("key") var a: String?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "3.0.23",
3
+ "version": "3.0.24",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -65,5 +65,5 @@
65
65
  "@testing-library/react-native": "^13.2.0",
66
66
  "expo-module-scripts": "^5.0.7"
67
67
  },
68
- "gitHead": "f1475b7bd0e8fdec0c0027be89c4c8d650d10805"
68
+ "gitHead": "1bba12a43e14a442f2cf1c73fe21968e0ef097c1"
69
69
  }