expo-modules-core 55.0.9 → 55.0.10

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 (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/compose/expo/modules/kotlin/views/ExpoComposeAndroidView.kt +10 -1
  4. package/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +62 -2
  5. package/android/src/compose/expo/modules/kotlin/views/ModuleDefinitionBuilderComposeExtension.kt +12 -3
  6. package/android/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMapBackingFieldAccessor.java +12 -0
  7. package/android/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMapHelper.kt +1 -19
  8. package/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt +37 -82
  9. package/android/src/main/java/expo/modules/adapters/react/services/UIManagerModuleWrapper.java +0 -119
  10. package/android/src/main/java/expo/modules/apploader/AppLoaderProvider.kt +2 -2
  11. package/android/src/main/java/expo/modules/apploader/HeadlessAppLoader.java +1 -15
  12. package/android/src/main/java/expo/modules/core/interfaces/services/UIManager.java +0 -30
  13. package/android/src/main/java/expo/modules/core/utilities/KotlinUtilities.kt +0 -11
  14. package/android/src/main/java/expo/modules/kotlin/apifeatures/Features.kt +1 -0
  15. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +5 -0
  16. package/android/src/main/java/expo/modules/kotlin/types/Either.kt +0 -4
  17. package/android/src/main/java/expo/modules/kotlin/types/EitherTypeConverter.kt +0 -4
  18. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +0 -2
  19. package/expo-module-gradle-plugin/src/main/kotlin/expo/modules/plugin/ProjectConfiguration.kt +4 -1
  20. package/expo-module-gradle-plugin/src/main/kotlin/expo/modules/plugin/android/MavenPublicationExtension.kt +50 -25
  21. package/expo-module-gradle-plugin/src/main/kotlin/expo/modules/plugin/gradle/ExpoModuleExtension.kt +13 -3
  22. package/ios/Core/AppContext.swift +1 -28
  23. package/ios/Core/Functions/AsyncFunctionDefinition.swift +0 -2
  24. package/ios/Core/Logging/PersistentFileLog.swift +5 -3
  25. package/ios/Core/ModuleHolder.swift +0 -17
  26. package/ios/Core/Views/ExpoView.swift +0 -39
  27. package/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift +0 -6
  28. package/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift +0 -4
  29. package/ios/Core/Views/ViewDefinition.swift +0 -5
  30. package/ios/ExpoModulesCore.h +0 -2
  31. package/ios/Fabric/ExpoFabricView.swift +0 -4
  32. package/ios/Fabric/ExpoFabricViewObjC.h +2 -10
  33. package/ios/Fabric/ExpoFabricViewObjC.mm +0 -4
  34. package/ios/Legacy/Services/EXReactNativeAdapter.h +1 -2
  35. package/package.json +2 -2
  36. package/FormatterTests.swift +0 -111
  37. package/android/src/main/java/expo/modules/adapters/react/apploader/HeadlessAppLoaderNotifier.kt +0 -31
  38. package/android/src/main/java/expo/modules/apploader/AppLoaderPackagesProviderInterface.java +0 -17
  39. package/android/src/main/java/expo/modules/core/MapHelper.java +0 -142
  40. package/android/src/main/java/expo/modules/core/errors/CurrentActivityNotFoundException.java +0 -14
  41. package/android/src/main/java/expo/modules/core/errors/ModuleNotFoundException.java +0 -14
  42. package/android/src/main/java/expo/modules/core/interfaces/Function.java +0 -7
  43. package/android/src/main/java/expo/modules/core/interfaces/services/KeepAwakeManager.java +0 -11
  44. package/android/src/main/java/expo/modules/interfaces/facedetector/FaceDetectionError.java +0 -7
  45. package/android/src/main/java/expo/modules/interfaces/facedetector/FaceDetectionSkipped.java +0 -7
  46. package/android/src/main/java/expo/modules/interfaces/facedetector/FaceDetectionUnspecifiedError.java +0 -4
  47. package/android/src/main/java/expo/modules/interfaces/facedetector/FaceDetectorInterface.java +0 -18
  48. package/android/src/main/java/expo/modules/interfaces/facedetector/FaceDetectorProviderInterface.java +0 -7
  49. package/android/src/main/java/expo/modules/interfaces/facedetector/FacesDetectionCompleted.java +0 -11
  50. package/ios/EXLegacyExpoViewProtocol.h +0 -13
  51. package/ios/Legacy/EXBridgeModule.h +0 -20
package/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.10 — 2026-02-20
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix crash from `PersistentFileLog`. ([#43283](https://github.com/expo/expo/pull/43283) by [@alanjhughes](https://github.com/alanjhughes))
18
+
19
+ ### 💡 Others
20
+
21
+ - Fixed view updates for Jetpack Compose integration. ([#42732](https://github.com/expo/expo/pull/42732) by [@kudo](https://github.com/kudo))
22
+ - [Android] Promoted `Either` type stable. ([#43267](https://github.com/expo/expo/pull/43267) by [@lukmccall](https://github.com/lukmccall))
23
+
13
24
  ## 55.0.9 — 2026-02-16
14
25
 
15
26
  ### 🐛 Bug fixes
@@ -20,6 +31,7 @@
20
31
  ### 💡 Others
21
32
 
22
33
  - Removed needless warning when `NativeModulesProxy` is absent. ([#43020](https://github.com/expo/expo/pull/43020) by [@tsapeta](https://github.com/tsapeta))
34
+ - [iOS] Removed some unused code. ([#42949](https://github.com/expo/expo/pull/42949) by [@tsapeta](https://github.com/tsapeta))
23
35
 
24
36
  ## 55.0.8 — 2026-02-08
25
37
 
@@ -47,6 +59,7 @@
47
59
 
48
60
  - [iOS] Fixed a crash in Fabric when unmounting a view while a geometry change event is being dispatched. ([#42628](https://github.com/expo/expo/issues/42628) by [@danishshaik](https://github.com/danishshaik)) ([#42634](https://github.com/expo/expo/pull/42634) by [@danishshaik](https://github.com/danishshaik))
49
61
  - [iOS] Fix crashes when converting a single JSValue into an Array. ([#42694](https://github.com/expo/expo/pull/42694) by [@behenate](https://github.com/behenate))
62
+ - Added more Jetpack Compose support. ([#42734](https://github.com/expo/expo/pull/42734) by [@kudo](https://github.com/kudo))
50
63
 
51
64
  ## 55.0.5 — 2026-01-27
52
65
 
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
29
29
  }
30
30
 
31
31
  group = 'host.exp.exponent'
32
- version = '55.0.9'
32
+ version = '55.0.10'
33
33
 
34
34
  def isExpoModulesCoreTests = {
35
35
  Gradle gradle = getGradle()
@@ -96,7 +96,7 @@ android {
96
96
  defaultConfig {
97
97
  consumerProguardFiles 'proguard-rules.pro'
98
98
  versionCode 1
99
- versionName "55.0.9"
99
+ versionName "55.0.10"
100
100
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
101
101
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
102
102
 
@@ -10,6 +10,13 @@ import androidx.compose.ui.viewinterop.AndroidView
10
10
  import com.facebook.react.uimanager.PixelUtil.pxToDp
11
11
  import expo.modules.kotlin.AppContext
12
12
 
13
+ /**
14
+ * Marks a view as capable of crossing the Jetpack Compose -> React Native boundary.
15
+ */
16
+ interface RNHostViewInterface {
17
+ var matchContents: Boolean
18
+ }
19
+
13
20
  /**
14
21
  * An ExpoComposeView for [AndroidView] wrapping with existing view
15
22
  */
@@ -17,7 +24,9 @@ import expo.modules.kotlin.AppContext
17
24
  internal class ExpoComposeAndroidView(
18
25
  private val view: View,
19
26
  appContext: AppContext
20
- ) : ExpoComposeView<ComposeProps>(view.context, appContext) {
27
+ ) : ExpoComposeView<ComposeProps>(view.context, appContext), RNHostViewInterface {
28
+ override var matchContents = false
29
+
21
30
  @Composable
22
31
  override fun ComposableScope.Content() {
23
32
  AndroidView(
@@ -8,22 +8,34 @@ import androidx.compose.foundation.layout.BoxScope
8
8
  import androidx.compose.foundation.layout.ColumnScope
9
9
  import androidx.compose.foundation.layout.RowScope
10
10
  import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.RecomposeScope
12
+ import androidx.compose.runtime.currentRecomposeScope
11
13
  import androidx.compose.runtime.getValue
12
14
  import androidx.compose.runtime.mutableStateOf
15
+ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
13
16
  import androidx.compose.ui.platform.ComposeView
14
17
  import androidx.compose.ui.platform.ViewCompositionStrategy
15
18
  import androidx.core.view.size
16
19
  import expo.modules.kotlin.AppContext
17
20
  import expo.modules.kotlin.viewevent.CoalescingKey
18
21
  import expo.modules.kotlin.viewevent.EventDispatcher
22
+ import expo.modules.kotlin.viewevent.ViewEvent
19
23
  import expo.modules.kotlin.viewevent.ViewEventDelegate
20
24
 
21
25
  data class ComposableScope(
22
26
  val rowScope: RowScope? = null,
23
27
  val columnScope: ColumnScope? = null,
24
- val boxScope: BoxScope? = null
28
+ val boxScope: BoxScope? = null,
29
+ val nestedScrollConnection: NestedScrollConnection? = null
25
30
  )
26
31
 
32
+ inline fun ComposableScope.withIf(
33
+ condition: Boolean,
34
+ block: ComposableScope.() -> ComposableScope
35
+ ): ComposableScope {
36
+ return if (condition) block() else this
37
+ }
38
+
27
39
  fun ComposableScope.with(rowScope: RowScope?): ComposableScope {
28
40
  return this.copy(rowScope = rowScope)
29
41
  }
@@ -36,6 +48,10 @@ fun ComposableScope.with(boxScope: BoxScope?): ComposableScope {
36
48
  return this.copy(boxScope = boxScope)
37
49
  }
38
50
 
51
+ fun ComposableScope.with(nestedScrollConnection: NestedScrollConnection?): ComposableScope {
52
+ return this.copy(nestedScrollConnection = nestedScrollConnection)
53
+ }
54
+
39
55
  /**
40
56
  * A base class that should be used by compose views.
41
57
  */
@@ -45,6 +61,16 @@ abstract class ExpoComposeView<T : ComposeProps>(
45
61
  private val withHostingView: Boolean = false
46
62
  ) : ExpoView(context, appContext) {
47
63
  open val props: T? = null
64
+ protected var recomposeScope: RecomposeScope? = null
65
+
66
+ private val globalEvent = ViewEvent<Pair<String, Map<String, Any?>>>(GLOBAL_EVENT_NAME, this, null)
67
+
68
+ /**
69
+ * A global event dispatcher
70
+ */
71
+ val globalEventDispatcher: (String, Map<String, Any?>) -> Unit = { name, params ->
72
+ globalEvent.invoke(Pair(name, params))
73
+ }
48
74
 
49
75
  @Composable
50
76
  abstract fun ComposableScope.Content()
@@ -78,8 +104,25 @@ abstract class ExpoComposeView<T : ComposeProps>(
78
104
 
79
105
  @Composable
80
106
  fun Children(composableScope: ComposableScope?) {
107
+ recomposeScope = currentRecomposeScope
108
+ for (index in 0..<this.size) {
109
+ val child = getChildAt(index) as? ExpoComposeView<*> ?: continue
110
+ with(composableScope ?: ComposableScope()) {
111
+ with(child) {
112
+ Content()
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ @Composable
119
+ fun Children(composableScope: ComposableScope?, filter: (child: ExpoComposeView<*>) -> Boolean) {
120
+ recomposeScope = currentRecomposeScope
81
121
  for (index in 0..<this.size) {
82
122
  val child = getChildAt(index) as? ExpoComposeView<*> ?: continue
123
+ if (!filter(child)) {
124
+ continue
125
+ }
83
126
  with(composableScope ?: ComposableScope()) {
84
127
  with(child) {
85
128
  Content()
@@ -90,6 +133,7 @@ abstract class ExpoComposeView<T : ComposeProps>(
90
133
 
91
134
  @Composable
92
135
  fun Child(composableScope: ComposableScope, index: Int) {
136
+ recomposeScope = currentRecomposeScope
93
137
  val child = getChildAt(index) as? ExpoComposeView<*> ?: return
94
138
  with(composableScope) {
95
139
  with(child) {
@@ -133,13 +177,23 @@ abstract class ExpoComposeView<T : ComposeProps>(
133
177
  }
134
178
 
135
179
  override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
136
- val view = if (child !is ExpoComposeView<*> && child !is ComposeView) {
180
+ val view = if (child !is ExpoComposeView<*> && child !is ComposeView && this !is RNHostViewInterface) {
137
181
  ExpoComposeAndroidView(child, appContext)
138
182
  } else {
139
183
  child
140
184
  }
141
185
  super.addView(view, index, params)
142
186
  }
187
+
188
+ override fun onViewAdded(child: View?) {
189
+ super.onViewAdded(child)
190
+ recomposeScope?.invalidate()
191
+ }
192
+
193
+ override fun onViewRemoved(child: View?) {
194
+ super.onViewRemoved(child)
195
+ recomposeScope?.invalidate()
196
+ }
143
197
  }
144
198
 
145
199
  /**
@@ -153,6 +207,7 @@ class FunctionalComposableScope(
153
207
  val composableScope: ComposableScope
154
208
  ) {
155
209
  val appContext = view.appContext
210
+ val globalEventDispatcher = view.globalEventDispatcher
156
211
 
157
212
  @Composable
158
213
  fun Child(composableScope: ComposableScope, index: Int) {
@@ -169,6 +224,11 @@ class FunctionalComposableScope(
169
224
  view.Children(composableScope)
170
225
  }
171
226
 
227
+ @Composable
228
+ fun Children(composableScope: ComposableScope?, filter: (child: ExpoComposeView<*>) -> Boolean) {
229
+ view.Children(composableScope, filter)
230
+ }
231
+
172
232
  inline fun <reified T> EventDispatcher(noinline coalescingKey: CoalescingKey<T>? = null): ViewEventDelegate<T> {
173
233
  return view.EventDispatcher<T>(coalescingKey)
174
234
  }
@@ -14,6 +14,11 @@ import kotlin.reflect.full.createInstance
14
14
  import kotlin.reflect.full.memberProperties
15
15
  import kotlin.reflect.typeOf
16
16
 
17
+ /**
18
+ * The name for the global event dispatcher
19
+ */
20
+ internal const val GLOBAL_EVENT_NAME = "onGlobalEvent"
21
+
17
22
  open class ModuleDefinitionBuilderWithCompose(
18
23
  module: Module? = null
19
24
  ) : InternalModuleDefinitionBuilder(module) {
@@ -54,7 +59,7 @@ class ComposeViewFunctionDefinitionBuilder<Props : ComposeProps>(
54
59
  val propsClass: KClass<Props>,
55
60
  val viewFunction: @Composable FunctionalComposableScope.(props: Props) -> Unit
56
61
  ) {
57
- private var callbacksDefinition: CallbacksDefinition? = null
62
+ private var callbacksDefinition: CallbacksDefinition = CallbacksDefinition(arrayOf(GLOBAL_EVENT_NAME))
58
63
 
59
64
  fun build(): ViewManagerDefinition {
60
65
  return ViewManagerDefinition(
@@ -80,7 +85,9 @@ class ComposeViewFunctionDefinitionBuilder<Props : ComposeProps>(
80
85
  * Defines prop names that should be treated as callbacks.
81
86
  */
82
87
  fun Events(vararg callbacks: String) {
83
- callbacksDefinition = CallbacksDefinition(callbacks)
88
+ callbacksDefinition = CallbacksDefinition(
89
+ arrayOf(GLOBAL_EVENT_NAME, *callbacks)
90
+ )
84
91
  }
85
92
 
86
93
  /**
@@ -88,6 +95,8 @@ class ComposeViewFunctionDefinitionBuilder<Props : ComposeProps>(
88
95
  */
89
96
  @JvmName("EventsWithArray")
90
97
  fun Events(callbacks: Array<String>) {
91
- callbacksDefinition = CallbacksDefinition(callbacks)
98
+ callbacksDefinition = CallbacksDefinition(
99
+ arrayOf(GLOBAL_EVENT_NAME, *callbacks)
100
+ )
92
101
  }
93
102
  }
@@ -0,0 +1,12 @@
1
+ package com.facebook.react.uimanager;
2
+
3
+ import com.facebook.react.bridge.ReadableMap;
4
+
5
+ /**
6
+ * Access the package private property declared inside of [ReactStylesDiffMap]
7
+ */
8
+ public class ReactStylesDiffMapBackingFieldAccessor {
9
+ static ReadableMap getBackingMap(ReactStylesDiffMap diffMap) {
10
+ return diffMap.internal_backingMap();
11
+ }
12
+ }
@@ -1,25 +1,7 @@
1
1
  package com.facebook.react.uimanager
2
2
 
3
3
  import com.facebook.react.bridge.ReadableMap
4
- import expo.modules.core.interfaces.DoNotStrip
5
- import java.lang.reflect.Field
6
4
 
7
- @get:DoNotStrip
8
- private val backingMapField: Field by lazy {
9
- ReactStylesDiffMap::class.java.getDeclaredField("backingMap").apply {
10
- isAccessible = true
11
- }
12
- }
13
-
14
- /**
15
- * Access the package private property declared inside of [ReactStylesDiffMap]
16
- * TODO: We should stop using this field and find a better way to access the backing map:
17
- * See: https://github.com/facebook/react-native/pull/51386
18
- */
19
5
  fun ReactStylesDiffMap.getBackingMap(): ReadableMap {
20
- return try {
21
- backingMapField.get(this) as ReadableMap
22
- } catch (e: ReflectiveOperationException) {
23
- throw RuntimeException("Unable to access internal_backingMap via reflection", e)
24
- }
6
+ return ReactStylesDiffMapBackingFieldAccessor.getBackingMap(this)
25
7
  }
@@ -4,68 +4,50 @@ import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import com.facebook.react.ReactApplication
6
6
  import com.facebook.react.ReactInstanceEventListener
7
- import com.facebook.react.ReactInstanceManager
8
7
  import com.facebook.react.bridge.ReactContext
9
8
  import com.facebook.react.common.LifecycleState
10
- import expo.modules.BuildConfig
11
9
  import expo.modules.apploader.HeadlessAppLoader
12
10
  import expo.modules.core.interfaces.Consumer
13
11
  import expo.modules.core.interfaces.DoNotStrip
14
12
 
15
13
  private val appRecords: MutableMap<String, ReactContext> = mutableMapOf()
16
14
 
17
- class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context) : HeadlessAppLoader {
15
+ class RNHeadlessAppLoader @DoNotStrip constructor() : HeadlessAppLoader {
18
16
 
19
17
  //region HeadlessAppLoader
20
18
 
21
- override fun loadApp(context: Context, params: HeadlessAppLoader.Params?, alreadyRunning: Runnable?, callback: Consumer<Boolean>?) {
19
+ override fun loadApp(
20
+ context: Context,
21
+ params: HeadlessAppLoader.Params?,
22
+ alreadyRunning: Runnable?,
23
+ callback: Consumer<Boolean>?
24
+ ) {
22
25
  if (params == null || params.appScopeKey == null) {
23
26
  throw IllegalArgumentException("Params must be set with appScopeKey!")
24
27
  }
25
28
 
26
- if (context.applicationContext is ReactApplication) {
27
- if (!appRecords.containsKey(params.appScopeKey)) {
28
- // In old arch reactHost will be null
29
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
30
- // New architecture
31
- val reactHost = (context.applicationContext 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
- // Ensure that we're starting the react host on the main thread
43
- android.os.Handler(context.mainLooper).post {
44
- reactHost.start()
45
- }
46
- } else {
47
- // Old architecture
48
- val reactInstanceManager = (context.applicationContext as ReactApplication).reactNativeHost.reactInstanceManager
49
- reactInstanceManager.addReactInstanceEventListener(
50
- object : ReactInstanceEventListener {
51
- override fun onReactContextInitialized(context: ReactContext) {
52
- HeadlessAppLoaderNotifier.notifyAppLoaded(params.appScopeKey)
53
- reactInstanceManager.removeReactInstanceEventListener(this)
54
- appRecords[params.appScopeKey] = context
55
- callback?.apply(true)
56
- }
57
- }
58
- )
59
- // Ensure that we're starting the react host on the main thread
60
- android.os.Handler(context.mainLooper).post {
61
- reactInstanceManager.createReactContextInBackground()
29
+ if (context.applicationContext !is ReactApplication) {
30
+ throw IllegalStateException("Your application must implement ReactApplication")
31
+ }
32
+
33
+ if (!appRecords.containsKey(params.appScopeKey)) {
34
+ val reactHost = (context.applicationContext as ReactApplication).reactHost
35
+ ?: throw IllegalStateException("Your application does not have a valid reactHost")
36
+ reactHost.addReactInstanceEventListener(
37
+ object : ReactInstanceEventListener {
38
+ override fun onReactContextInitialized(context: ReactContext) {
39
+ reactHost.removeReactInstanceEventListener(this)
40
+ appRecords[params.appScopeKey] = context
41
+ callback?.apply(true)
62
42
  }
63
43
  }
64
- } else {
65
- alreadyRunning?.run()
44
+ )
45
+ // Ensure that we're starting the react host on the main thread
46
+ android.os.Handler(context.mainLooper).post {
47
+ reactHost.start()
66
48
  }
67
49
  } else {
68
- throw IllegalStateException("Your application must implement ReactApplication")
50
+ alreadyRunning?.run()
69
51
  }
70
52
  }
71
53
 
@@ -73,35 +55,16 @@ class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context)
73
55
  override fun invalidateApp(appScopeKey: String?): Boolean {
74
56
  return if (appRecords.containsKey(appScopeKey) && appRecords[appScopeKey] != null) {
75
57
  val reactContext = appRecords[appScopeKey] ?: return false
76
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
77
- // New architecture
78
- val reactHost = (reactContext.applicationContext as ReactApplication).reactHost ?: throw IllegalStateException("Your application does not have a valid reactHost")
79
- android.os.Handler(reactContext.mainLooper).post {
80
- // Only destroy the `ReactInstanceManager` if it does not bind with an Activity.
81
- // And The Activity would take over the ownership of `ReactInstanceManager`.
82
- // This case happens when a user clicks a background task triggered notification immediately.
83
- if (reactHost.lifecycleState == LifecycleState.BEFORE_CREATE) {
84
- reactHost.destroy("Closing headless task app", null)
85
- }
86
- HeadlessAppLoaderNotifier.notifyAppDestroyed(appScopeKey)
87
- appRecords.remove(appScopeKey)
88
- }
89
- } else {
90
- // Old architecture
91
- val reactNativeHost = (reactContext.applicationContext as ReactApplication).reactNativeHost
92
- if (reactNativeHost.hasInstance()) {
93
- val reactInstanceManager: ReactInstanceManager = reactNativeHost.reactInstanceManager
94
- android.os.Handler(reactContext.mainLooper).post {
95
- // Only destroy the `ReactInstanceManager` if it does not bind with an Activity.
96
- // And The Activity would take over the ownership of `ReactInstanceManager`.
97
- // This case happens when a user clicks a background task triggered notification immediately.
98
- if (reactInstanceManager.lifecycleState == LifecycleState.BEFORE_CREATE) {
99
- reactInstanceManager.destroy()
100
- }
101
- HeadlessAppLoaderNotifier.notifyAppDestroyed(appScopeKey)
102
- appRecords.remove(appScopeKey)
103
- }
58
+ val reactHost = (reactContext.applicationContext as ReactApplication).reactHost
59
+ ?: throw IllegalStateException("Your application does not have a valid reactHost")
60
+ android.os.Handler(reactContext.mainLooper).post {
61
+ // Only destroy the `ReactInstanceManager` if it does not bind with an Activity.
62
+ // And The Activity would take over the ownership of `ReactInstanceManager`.
63
+ // This case happens when a user clicks a background task triggered notification immediately.
64
+ if (reactHost.lifecycleState == LifecycleState.BEFORE_CREATE) {
65
+ reactHost.destroy("Closing headless task app", null)
104
66
  }
67
+ appRecords.remove(appScopeKey)
105
68
  }
106
69
  true
107
70
  } else {
@@ -110,17 +73,9 @@ class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context)
110
73
  }
111
74
 
112
75
  override fun isRunning(appScopeKey: String?): Boolean {
113
- val reactContext = appRecords[appScopeKey] ?: return false
114
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
115
- // New architecture - We can return true since the fact that we have a reactContext
116
- // means that we've already called start on the reactHost
117
- return true
118
- } else {
119
- // Old architecture
120
- val reactNativeHost = (reactContext.applicationContext as ReactApplication).reactNativeHost
121
- return reactNativeHost.reactInstanceManager.hasStartedCreatingInitialContext()
122
- }
76
+ // New architecture - We can return true since the fact that we have a reactContext
77
+ // means that we've already called start on the reactHost
78
+ return appRecords[appScopeKey] != null
123
79
  }
124
-
125
80
  //endregion HeadlessAppLoader
126
81
  }
@@ -2,32 +2,20 @@ package expo.modules.adapters.react.services;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.Intent;
5
- import android.util.Log;
6
- import android.view.View;
7
5
 
8
6
  import com.facebook.react.bridge.ReactContext;
9
7
  import com.facebook.react.common.annotations.FrameworkAPI;
10
8
  import com.facebook.react.common.annotations.UnstableReactNativeAPI;
11
- import com.facebook.react.fabric.FabricUIManager;
12
- import com.facebook.react.fabric.interop.UIBlockViewResolver;
13
9
  import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
14
- import com.facebook.react.uimanager.IllegalViewOperationException;
15
- import com.facebook.react.uimanager.NativeViewHierarchyManager;
16
- import com.facebook.react.uimanager.UIManagerHelper;
17
- import com.facebook.react.uimanager.UIManagerModule;
18
- import com.facebook.react.uimanager.common.UIManagerType;
19
10
 
20
11
  import java.lang.ref.WeakReference;
21
12
  import java.util.ArrayList;
22
13
  import java.util.Arrays;
23
14
  import java.util.List;
24
15
  import java.util.Map;
25
- import java.util.Objects;
26
16
  import java.util.WeakHashMap;
27
17
 
28
- import androidx.annotation.Nullable;
29
18
  import androidx.annotation.OptIn;
30
- import expo.modules.BuildConfig;
31
19
  import expo.modules.core.interfaces.ActivityEventListener;
32
20
  import expo.modules.core.interfaces.ActivityProvider;
33
21
  import expo.modules.core.interfaces.InternalModule;
@@ -62,99 +50,6 @@ public class UIManagerModuleWrapper implements
62
50
  );
63
51
  }
64
52
 
65
- private void addToUIManager(final UIBlockInterface block) {
66
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
67
- com.facebook.react.bridge.UIManager uiManager = UIManagerHelper.getUIManager(getContext(), UIManagerType.FABRIC);
68
- Objects.requireNonNull(((FabricUIManager) uiManager)).addUIBlock(block);
69
- } else {
70
- UIManagerModule uiManager = getContext().getNativeModule(UIManagerModule.class);
71
- Objects.requireNonNull(uiManager).addUIBlock(block);
72
- }
73
- }
74
-
75
- @Override
76
- @SuppressWarnings("deprecation")
77
- public <T> void addUIBlock(final int tag, final UIBlock<T> block, final Class<T> tClass) {
78
- UIBlockInterface uiBlock = new UIBlockInterface() {
79
- @Override
80
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
81
- executeImpl(nativeViewHierarchyManager, null);
82
- }
83
-
84
- @Override
85
- public void execute(UIBlockViewResolver uiBlockViewResolver) {
86
- executeImpl(null, uiBlockViewResolver);
87
- }
88
-
89
- private void executeImpl(NativeViewHierarchyManager nativeViewHierarchyManager, UIBlockViewResolver uiBlockViewResolver) {
90
- View view = nativeViewHierarchyManager.resolveView(tag);
91
- if (view == null) {
92
- block.reject(new IllegalArgumentException("Expected view for this tag not to be null."));
93
- } else {
94
- try {
95
- if (tClass.isInstance(view)) {
96
- block.resolve(tClass.cast(view));
97
- } else {
98
- block.reject(new IllegalStateException(
99
- "Expected view to be of " + tClass + "; found " + view.getClass() + " instead"));
100
- }
101
- } catch (Exception e) {
102
- block.reject(e);
103
- }
104
- }
105
- }
106
- };
107
-
108
- addToUIManager(uiBlock);
109
- }
110
-
111
- @Override
112
- @SuppressWarnings("deprecation")
113
- public void addUIBlock(final GroupUIBlock block) {
114
- UIBlockInterface uiBlock = new UIBlockInterface() {
115
- @Override
116
- public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
117
- executeImpl(nativeViewHierarchyManager, null);
118
- }
119
-
120
- @Override
121
- public void execute(UIBlockViewResolver uiBlockViewResolver) {
122
- executeImpl(null, uiBlockViewResolver);
123
- }
124
-
125
- private void executeImpl(NativeViewHierarchyManager nativeViewHierarchyManager, UIBlockViewResolver uiBlockViewResolver) {
126
- block.execute(new ViewHolder() {
127
- @Override
128
- public View get(Object key) {
129
- if (key instanceof Number) {
130
- try {
131
- return nativeViewHierarchyManager.resolveView(((Number) key).intValue());
132
- } catch (IllegalViewOperationException e) {
133
- return null;
134
- }
135
- } else {
136
- Log.w("E_INVALID_TAG", "Provided tag is of class " + key.getClass() + " whereas React expects tags to be integers. Are you sure you're providing proper argument to addUIBlock?");
137
- }
138
- return null;
139
- }
140
- });
141
- }
142
- };
143
-
144
- addToUIManager(uiBlock);
145
- }
146
-
147
- @Nullable
148
- @Override
149
- @SuppressWarnings("deprecation")
150
- public View resolveView(int viewTag) {
151
- final com.facebook.react.bridge.UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getContext(), viewTag);
152
- if (uiManager == null) {
153
- return null;
154
- }
155
- return uiManager.resolveView(viewTag);
156
- }
157
-
158
53
  @Override
159
54
  public void runOnUiQueueThread(Runnable runnable) {
160
55
  if (getContext().isOnUiQueueThread()) {
@@ -173,15 +68,6 @@ public class UIManagerModuleWrapper implements
173
68
  }
174
69
  }
175
70
 
176
- public void runOnNativeModulesQueueThread(Runnable runnable) {
177
- if (mReactContext.isOnNativeModulesQueueThread()) {
178
- runnable.run();
179
- } else {
180
- mReactContext.runOnNativeModulesQueueThread(runnable);
181
- }
182
- }
183
-
184
-
185
71
  @Override
186
72
  public void registerLifecycleEventListener(final LifecycleEventListener listener) {
187
73
  final WeakReference<LifecycleEventListener> weakListener = new WeakReference<>(listener);
@@ -279,8 +165,3 @@ public class UIManagerModuleWrapper implements
279
165
  return getContext().getCurrentActivity();
280
166
  }
281
167
  }
282
-
283
- @OptIn(markerClass = UnstableReactNativeAPI.class)
284
- @SuppressWarnings("deprecation")
285
- interface UIBlockInterface extends com.facebook.react.uimanager.UIBlock, com.facebook.react.fabric.interop.UIBlock {
286
- }
@@ -33,8 +33,8 @@ object AppLoaderProvider {
33
33
 
34
34
  loaderClass = Class.forName(loaderClassName) as Class<out HeadlessAppLoader>
35
35
  loaders[name] = loaderClass
36
- .getDeclaredConstructor(Context::class.java)
37
- .newInstance(context) as HeadlessAppLoader
36
+ .getDeclaredConstructor()
37
+ .newInstance() as HeadlessAppLoader
38
38
  } catch (e: PackageManager.NameNotFoundException) {
39
39
  throw IllegalStateException("Unable to instantiate AppLoader!", e)
40
40
  }
@@ -5,19 +5,7 @@ import android.content.Context;
5
5
  import expo.modules.core.interfaces.Consumer;
6
6
 
7
7
  public interface HeadlessAppLoader {
8
-
9
- class AppConfigurationError extends Exception {
10
-
11
- public AppConfigurationError(String message) {
12
- super(message);
13
- }
14
-
15
- public AppConfigurationError(String message, Exception cause) {
16
- super(message, cause);
17
- }
18
- }
19
-
20
- void loadApp(Context context, Params params, Runnable alreadyRunning, Consumer<Boolean> callback) throws AppConfigurationError;
8
+ void loadApp(Context context, Params params, Runnable alreadyRunning, Consumer<Boolean> callback);
21
9
 
22
10
  boolean invalidateApp(String appScopeKey);
23
11
 
@@ -39,7 +27,5 @@ public interface HeadlessAppLoader {
39
27
  public String getAppUrl() {
40
28
  return appUrl;
41
29
  }
42
-
43
30
  }
44
-
45
31
  }