expo-modules-core 2.0.0-preview.1 → 2.0.0-preview.3
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 +16 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt +68 -21
- package/android/src/main/java/expo/modules/kotlin/devtools/ExpoRequestCdpInterceptor.kt +4 -3
- package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +0 -22
- package/android/src/main/java/expo/modules/kotlin/modules/ModuleUtils.kt +25 -0
- package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +15 -0
- package/ios/Core/DynamicTypes/DynamicOptionalType.swift +1 -1
- package/ios/Core/SharedObjects/SharedRef.swift +1 -1
- package/ios/DevTools/ExpoRequestCdpInterceptor.swift +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 2.0.0-preview.3 — 2024-10-24
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 2.0.0-preview.2 — 2024-10-24
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug fixes
|
|
20
|
+
|
|
21
|
+
- [iOS] Fix optionals conversion. ([#32239](https://github.com/expo/expo/pull/32239) by [@aleqsio](https://github.com/aleqsio))
|
|
22
|
+
- Fixed retain cycle for `ExpoRequestCdpInterceptor`. ([#32289](https://github.com/expo/expo/pull/32289) by [@kudo](https://github.com/kudo))
|
|
23
|
+
|
|
24
|
+
### 💡 Others
|
|
25
|
+
|
|
26
|
+
- [android] Add enum event support to OnStartObserving and OnStopObserving. ([#32251](https://github.com/expo/expo/pull/32251), [#32287](https://github.com/expo/expo/pull/32287) by [@wschurman](https://github.com/wschurman))
|
|
27
|
+
|
|
13
28
|
## 2.0.0-preview.1 — 2024-10-22
|
|
14
29
|
|
|
15
30
|
### 🐛 Bug fixes
|
|
@@ -56,6 +71,7 @@
|
|
|
56
71
|
|
|
57
72
|
### 🐛 Bug fixes
|
|
58
73
|
|
|
74
|
+
- [Android] Fixed `RNHeadlessAppLoader` class for New Architecture support. ([#32146](https://github.com/expo/expo/pull/32146) by [@chrfalch](https://github.com/chrfalch))
|
|
59
75
|
- [iOS] Fix using enums as optional arguments. ([#32147](https://github.com/expo/expo/pull/32147) by [@aleqsio](https://github.com/aleqsio))
|
|
60
76
|
- [Android] Change JS return type for kotlin `null` to be `null` instead of `undefined`. ([#31301](https://github.com/expo/expo/pull/31301) by [@aleqsio](https://github.com/aleqsio))
|
|
61
77
|
- [iOS] Swift `Enumerable`s did not correctly convert to JS values. ([#30191](https://github.com/expo/expo/pull/30191) by [@vonovak](https://github.com/vonovak))
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
|
3
3
|
apply plugin: 'com.android.library'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '2.0.0-preview.
|
|
6
|
+
version = '2.0.0-preview.3'
|
|
7
7
|
|
|
8
8
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
9
9
|
apply from: expoModulesCorePlugin
|
|
@@ -67,7 +67,7 @@ android {
|
|
|
67
67
|
defaultConfig {
|
|
68
68
|
consumerProguardFiles 'proguard-rules.pro'
|
|
69
69
|
versionCode 1
|
|
70
|
-
versionName "2.0.0-preview.
|
|
70
|
+
versionName "2.0.0-preview.3"
|
|
71
71
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
|
|
72
72
|
|
|
73
73
|
testInstrumentationRunner "expo.modules.TestRunner"
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
package expo.modules.adapters.react.apploader
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
3
4
|
import android.content.Context
|
|
4
5
|
import com.facebook.react.ReactApplication
|
|
5
6
|
import com.facebook.react.ReactInstanceEventListener
|
|
6
7
|
import com.facebook.react.ReactInstanceManager
|
|
7
8
|
import com.facebook.react.bridge.ReactContext
|
|
8
9
|
import com.facebook.react.common.LifecycleState
|
|
10
|
+
import expo.modules.BuildConfig
|
|
9
11
|
import expo.modules.apploader.HeadlessAppLoader
|
|
10
12
|
import expo.modules.core.interfaces.Consumer
|
|
11
13
|
import expo.modules.core.interfaces.DoNotStrip
|
|
12
14
|
|
|
13
|
-
private val appRecords: MutableMap<String,
|
|
15
|
+
private val appRecords: MutableMap<String, ReactContext> = mutableMapOf()
|
|
14
16
|
|
|
15
17
|
class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context) : HeadlessAppLoader {
|
|
16
18
|
|
|
@@ -22,16 +24,35 @@ class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context)
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
if (context.applicationContext is ReactApplication) {
|
|
25
|
-
val reactInstanceManager = (context.applicationContext as ReactApplication).reactNativeHost.reactInstanceManager
|
|
26
27
|
if (!appRecords.containsKey(params.appScopeKey)) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
// In old arch reactHost will be null
|
|
29
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
30
|
+
// New architecture
|
|
31
|
+
val reactHost = (context as ReactApplication).reactHost ?: throw IllegalStateException("Your application does not have a valid reactHost")
|
|
32
|
+
reactHost.addReactInstanceEventListener(
|
|
33
|
+
object : ReactInstanceEventListener {
|
|
34
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
35
|
+
reactHost.removeReactInstanceEventListener(this)
|
|
36
|
+
HeadlessAppLoaderNotifier.notifyAppLoaded(params.appScopeKey)
|
|
37
|
+
appRecords[params.appScopeKey] = context
|
|
38
|
+
callback?.apply(true)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
reactHost.start()
|
|
43
|
+
} else {
|
|
44
|
+
// Old architecture
|
|
45
|
+
val reactInstanceManager = (context as ReactApplication).reactNativeHost.reactInstanceManager
|
|
46
|
+
reactInstanceManager.addReactInstanceEventListener(
|
|
47
|
+
object : ReactInstanceEventListener {
|
|
48
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
49
|
+
HeadlessAppLoaderNotifier.notifyAppLoaded(params.appScopeKey)
|
|
50
|
+
reactInstanceManager.removeReactInstanceEventListener(this)
|
|
51
|
+
appRecords[params.appScopeKey] = context
|
|
52
|
+
callback?.apply(true)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
35
56
|
reactInstanceManager.createReactContextInBackground()
|
|
36
57
|
}
|
|
37
58
|
} else {
|
|
@@ -42,18 +63,34 @@ class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context)
|
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
65
|
|
|
66
|
+
@SuppressLint("VisibleForTests")
|
|
45
67
|
override fun invalidateApp(appScopeKey: String?): Boolean {
|
|
46
68
|
return if (appRecords.containsKey(appScopeKey) && appRecords[appScopeKey] != null) {
|
|
47
|
-
val
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
val reactContext = appRecords[appScopeKey] ?: return false
|
|
70
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
71
|
+
// New architecture
|
|
72
|
+
val reactHost = (reactContext.baseContext as ReactApplication).reactHost ?: throw IllegalStateException("Your application does not have a valid reactHost")
|
|
73
|
+
android.os.Handler(reactContext.mainLooper).post {
|
|
74
|
+
reactHost.destroy("Closing headless task app", null)
|
|
75
|
+
HeadlessAppLoaderNotifier.notifyAppDestroyed(appScopeKey)
|
|
76
|
+
appRecords.remove(appScopeKey)
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Old architecture
|
|
80
|
+
val reactNativeHost = (reactContext as ReactApplication).reactNativeHost
|
|
81
|
+
if (reactNativeHost.hasInstance()) {
|
|
82
|
+
val reactInstanceManager: ReactInstanceManager = reactNativeHost.reactInstanceManager
|
|
83
|
+
android.os.Handler(reactContext.mainLooper).post {
|
|
84
|
+
// Only destroy the `ReactInstanceManager` if it does not bind with an Activity.
|
|
85
|
+
// And The Activity would take over the ownership of `ReactInstanceManager`.
|
|
86
|
+
// This case happens when a user clicks a background task triggered notification immediately.
|
|
87
|
+
if (reactInstanceManager.lifecycleState == LifecycleState.BEFORE_CREATE) {
|
|
88
|
+
reactInstanceManager.destroy()
|
|
89
|
+
}
|
|
90
|
+
HeadlessAppLoaderNotifier.notifyAppDestroyed(appScopeKey)
|
|
91
|
+
appRecords.remove(appScopeKey)
|
|
92
|
+
}
|
|
54
93
|
}
|
|
55
|
-
HeadlessAppLoaderNotifier.notifyAppDestroyed(appScopeKey)
|
|
56
|
-
appRecords.remove(appScopeKey)
|
|
57
94
|
}
|
|
58
95
|
true
|
|
59
96
|
} else {
|
|
@@ -61,8 +98,18 @@ class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context)
|
|
|
61
98
|
}
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
override fun isRunning(appScopeKey: String?): Boolean
|
|
65
|
-
|
|
101
|
+
override fun isRunning(appScopeKey: String?): Boolean {
|
|
102
|
+
val reactContext = appRecords[appScopeKey] ?: return false
|
|
103
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
104
|
+
// New architecture - We can return true since the fact that we have a reactContext
|
|
105
|
+
// means that we've already called start on the reactHost
|
|
106
|
+
return true
|
|
107
|
+
} else {
|
|
108
|
+
// Old architecture
|
|
109
|
+
val reactNativeHost = (reactContext.baseContext as ReactApplication).reactNativeHost
|
|
110
|
+
return reactNativeHost.reactInstanceManager.hasStartedCreatingInitialContext()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
66
113
|
|
|
67
114
|
//endregion HeadlessAppLoader
|
|
68
115
|
}
|
|
@@ -14,6 +14,7 @@ import kotlinx.coroutines.launch
|
|
|
14
14
|
import okhttp3.Request
|
|
15
15
|
import okhttp3.Response
|
|
16
16
|
import okhttp3.ResponseBody
|
|
17
|
+
import java.lang.ref.WeakReference
|
|
17
18
|
import java.math.BigDecimal
|
|
18
19
|
import java.math.RoundingMode
|
|
19
20
|
|
|
@@ -22,18 +23,18 @@ import java.math.RoundingMode
|
|
|
22
23
|
* dispatch CDP (Chrome DevTools Protocol: https://chromedevtools.github.io/devtools-protocol/) events.
|
|
23
24
|
*/
|
|
24
25
|
object ExpoRequestCdpInterceptor : ExpoNetworkInspectOkHttpInterceptorsDelegate {
|
|
25
|
-
private var delegate: Delegate
|
|
26
|
+
private var delegate: WeakReference<Delegate?> = WeakReference(null)
|
|
26
27
|
internal var coroutineScope = CoroutineScope(Dispatchers.Default)
|
|
27
28
|
|
|
28
29
|
fun setDelegate(delegate: Delegate?) {
|
|
29
30
|
coroutineScope.launch {
|
|
30
|
-
this@ExpoRequestCdpInterceptor.delegate = delegate
|
|
31
|
+
this@ExpoRequestCdpInterceptor.delegate = WeakReference(delegate)
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
private fun dispatchEvent(event: Event) {
|
|
35
36
|
coroutineScope.launch {
|
|
36
|
-
this@ExpoRequestCdpInterceptor.delegate?.dispatch(event.toJson())
|
|
37
|
+
this@ExpoRequestCdpInterceptor.delegate.get()?.dispatch(event.toJson())
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -7,9 +7,6 @@ import expo.modules.kotlin.providers.AppContextProvider
|
|
|
7
7
|
import expo.modules.kotlin.tracing.trace
|
|
8
8
|
import expo.modules.kotlin.types.Enumerable
|
|
9
9
|
import kotlinx.coroutines.CoroutineScope
|
|
10
|
-
import kotlin.reflect.KProperty1
|
|
11
|
-
import kotlin.reflect.full.declaredMemberProperties
|
|
12
|
-
import kotlin.reflect.full.primaryConstructor
|
|
13
10
|
|
|
14
11
|
abstract class Module : AppContextProvider {
|
|
15
12
|
|
|
@@ -54,25 +51,6 @@ abstract class Module : AppContextProvider {
|
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
abstract fun definition(): ModuleDefinitionData
|
|
57
|
-
|
|
58
|
-
private fun <T> convertEnumToString(enumValue: T): String where T : Enumerable, T : Enum<T> {
|
|
59
|
-
val enumClass = enumValue::class
|
|
60
|
-
val primaryConstructor = enumClass.primaryConstructor
|
|
61
|
-
if (primaryConstructor?.parameters?.size == 1) {
|
|
62
|
-
val parameterName = primaryConstructor.parameters.first().name
|
|
63
|
-
val parameterProperty = enumClass
|
|
64
|
-
.declaredMemberProperties
|
|
65
|
-
.find { it.name == parameterName }
|
|
66
|
-
|
|
67
|
-
requireNotNull(parameterProperty) { "Cannot find a property for $parameterName parameter" }
|
|
68
|
-
require(parameterProperty.returnType.classifier == String::class) { "The enum parameter has to be a string." }
|
|
69
|
-
|
|
70
|
-
@Suppress("UNCHECKED_CAST")
|
|
71
|
-
return (parameterProperty as KProperty1<T, String>).get(enumValue)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return enumValue.name
|
|
75
|
-
}
|
|
76
54
|
}
|
|
77
55
|
|
|
78
56
|
@Suppress("FunctionName")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package expo.modules.kotlin.modules
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.types.Enumerable
|
|
4
|
+
import kotlin.reflect.KProperty1
|
|
5
|
+
import kotlin.reflect.full.declaredMemberProperties
|
|
6
|
+
import kotlin.reflect.full.primaryConstructor
|
|
7
|
+
|
|
8
|
+
internal fun <T> convertEnumToString(enumValue: T): String where T : Enumerable, T : Enum<T> {
|
|
9
|
+
val enumClass = enumValue::class
|
|
10
|
+
val primaryConstructor = enumClass.primaryConstructor
|
|
11
|
+
if (primaryConstructor?.parameters?.size == 1) {
|
|
12
|
+
val parameterName = primaryConstructor.parameters.first().name
|
|
13
|
+
val parameterProperty = enumClass
|
|
14
|
+
.declaredMemberProperties
|
|
15
|
+
.find { it.name == parameterName }
|
|
16
|
+
|
|
17
|
+
requireNotNull(parameterProperty) { "Cannot find a property for $parameterName parameter" }
|
|
18
|
+
require(parameterProperty.returnType.classifier == String::class) { "The enum parameter has to be a string." }
|
|
19
|
+
|
|
20
|
+
@Suppress("UNCHECKED_CAST")
|
|
21
|
+
return (parameterProperty as KProperty1<T, String>).get(enumValue)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return enumValue.name
|
|
25
|
+
}
|
|
@@ -18,6 +18,7 @@ import expo.modules.kotlin.jni.JavaScriptModuleObject
|
|
|
18
18
|
import expo.modules.kotlin.jni.decorators.JSDecoratorsBridgingObject
|
|
19
19
|
import expo.modules.kotlin.modules.Module
|
|
20
20
|
import expo.modules.kotlin.modules.ModuleDefinitionBuilder
|
|
21
|
+
import expo.modules.kotlin.modules.convertEnumToString
|
|
21
22
|
import expo.modules.kotlin.types.Enumerable
|
|
22
23
|
import expo.modules.kotlin.types.enforceType
|
|
23
24
|
import expo.modules.kotlin.types.toArgsArray
|
|
@@ -473,6 +474,13 @@ open class ObjectDefinitionBuilder {
|
|
|
473
474
|
}
|
|
474
475
|
}
|
|
475
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Creates module's lifecycle listener that is called right after the first event listener is added for given event.
|
|
479
|
+
*/
|
|
480
|
+
fun <T> OnStartObserving(enum: T, body: () -> Unit) where T : Enumerable, T : Enum<T> {
|
|
481
|
+
OnStartObserving(convertEnumToString(enum), body)
|
|
482
|
+
}
|
|
483
|
+
|
|
476
484
|
/**
|
|
477
485
|
* Creates module's lifecycle listener that is called right after the first event listener is added.
|
|
478
486
|
*/
|
|
@@ -499,6 +507,13 @@ open class ObjectDefinitionBuilder {
|
|
|
499
507
|
}
|
|
500
508
|
}
|
|
501
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Creates module's lifecycle listener that is called right after all event listeners are removed for given event.
|
|
512
|
+
*/
|
|
513
|
+
fun <T> OnStopObserving(enum: T, body: () -> Unit) where T : Enumerable, T : Enum<T> {
|
|
514
|
+
OnStopObserving(convertEnumToString(enum), body)
|
|
515
|
+
}
|
|
516
|
+
|
|
502
517
|
/**
|
|
503
518
|
* Creates module's lifecycle listener that is called right after all event listeners are removed.
|
|
504
519
|
*/
|
|
@@ -25,7 +25,7 @@ internal struct DynamicOptionalType: AnyDynamicType {
|
|
|
25
25
|
if jsValue.isUndefined() || jsValue.isNull() {
|
|
26
26
|
return Optional<Any>.none as Any
|
|
27
27
|
}
|
|
28
|
-
return try
|
|
28
|
+
return try wrappedType.cast(jsValue: jsValue, appContext: appContext)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
func cast<ValueType>(_ value: ValueType, appContext: AppContext) throws -> Any {
|
|
@@ -10,7 +10,7 @@ public protocol AnySharedRef {
|
|
|
10
10
|
to native instances among different independent libraries.
|
|
11
11
|
*/
|
|
12
12
|
open class SharedRef<RefType>: SharedObject, AnySharedRef {
|
|
13
|
-
public
|
|
13
|
+
public var ref: RefType
|
|
14
14
|
|
|
15
15
|
open var nativeRefType: String {
|
|
16
16
|
"unknown"
|
|
@@ -8,7 +8,7 @@ import Foundation
|
|
|
8
8
|
*/
|
|
9
9
|
@objc(EXRequestCdpInterceptor)
|
|
10
10
|
public final class ExpoRequestCdpInterceptor: NSObject, ExpoRequestInterceptorProtocolDelegate {
|
|
11
|
-
private var delegate: ExpoRequestCdpInterceptorDelegate?
|
|
11
|
+
private weak var delegate: ExpoRequestCdpInterceptorDelegate?
|
|
12
12
|
public var dispatchQueue = DispatchQueue(label: "expo.requestCdpInterceptor")
|
|
13
13
|
|
|
14
14
|
override private init() {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-core",
|
|
3
|
-
"version": "2.0.0-preview.
|
|
3
|
+
"version": "2.0.0-preview.3",
|
|
4
4
|
"description": "The core of Expo Modules architecture",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"@testing-library/react-native": "^12.5.2",
|
|
45
45
|
"expo-module-scripts": "^4.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "2e4f18d41da033c5ced0a4a045d91cf5250016b7"
|
|
48
48
|
}
|