expo-modules-core 56.0.16 → 56.0.18

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 (32) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +58 -7
  4. package/android/src/main/cpp/JavaScriptObject.cpp +6 -0
  5. package/android/src/main/cpp/JavaScriptObject.h +5 -0
  6. package/android/src/main/cpp/fabric/NativeStatePropsGetter.cpp +2 -2
  7. package/android/src/main/cpp/installers/WorkletRuntimeInstaller.cpp +25 -1
  8. package/android/src/main/cpp/installers/WorkletRuntimeInstaller.h +9 -0
  9. package/android/src/main/java/expo/modules/kotlin/defaultmodules/CoreModule.kt +9 -18
  10. package/android/src/main/java/expo/modules/kotlin/jni/WorkletRuntimeInstaller.kt +11 -0
  11. package/android/src/main/java/expo/modules/kotlin/jni/fabric/NativeStatePropsGetter.kt +26 -2
  12. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +9 -0
  13. package/build/ts-declarations/global.d.ts +4 -3
  14. package/build/ts-declarations/global.d.ts.map +1 -1
  15. package/build/worklets/index.d.ts +8 -1
  16. package/build/worklets/index.d.ts.map +1 -1
  17. package/ios/Core/AppContext.swift +7 -2
  18. package/ios/Core/Modules/CoreModule.swift +3 -10
  19. package/ios/Worklets/Core/WorkletIntegration.swift +7 -14
  20. package/ios/Worklets/EXWorkletsProvider.h +4 -0
  21. package/ios/Worklets/WorkletRuntimeResolver.h +23 -0
  22. package/ios/Worklets/WorkletRuntimeResolver.mm +15 -0
  23. package/ios/WorkletsAdapter/ExpoWorkletsBridgeProvider.mm +21 -0
  24. package/ios/WorkletsTests/WorkletsNotInstalledTests.swift +20 -0
  25. package/package.json +3 -3
  26. package/prebuilds/output/debug/xcframeworks/ExpoModulesCore.tar.gz +0 -0
  27. package/prebuilds/output/debug/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
  28. package/prebuilds/output/release/xcframeworks/ExpoModulesCore.tar.gz +0 -0
  29. package/prebuilds/output/release/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
  30. package/src/polyfill/dangerous-internal.ts +1 -1
  31. package/src/ts-declarations/global.ts +4 -3
  32. package/src/worklets/index.ts +10 -3
package/CHANGELOG.md CHANGED
@@ -10,6 +10,24 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.18 — 2026-07-01
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android][compose] Guard `onLayout` against detached window to prevent `LayoutNode should be attached to an owner` crash. ([#47085](https://github.com/expo/expo/pull/47085) by [@roitium](https://github.com/roitium))
18
+ - [Android] Fix Jetpack Compose `Host` content disappearing before a react-native-screens pop animation finishes, by deferring composition disposal to window detach while the view is still on-screen for a transition. ([#45914](https://github.com/expo/expo/issues/45914), [#47086](https://github.com/expo/expo/issues/47086) by [@aubrey-wodonga](https://github.com/aubrey-wodonga)) ([#47099](https://github.com/expo/expo/pull/47099) by [@nishan](https://github.com/intergalacticspacehighway))
19
+ - [iOS] Fix a reload deadlock on the new architecture where reloading (from pressing `r` in the iOS simulator) would freeze the app until the watchdog killed it. `reloadAppAsync` now triggers the reload synchronously when already on the main thread instead of always deferring via `DispatchQueue.main.async`. ([#47392](https://github.com/expo/expo/pull/47392) by [@brentvatne](https://github.com/brentvatne))
20
+
21
+ ### 💡 Others
22
+
23
+ - [iOS][android] Resolve the worklet UI runtime from its `react-native-worklets` holder instead of the reanimated `_WORKLET_RUNTIME` global; `installOnUIRuntime` now takes the holder from `getUIRuntimeHolder()`. ([#46922](https://github.com/expo/expo/pull/46922), [#46935](https://github.com/expo/expo/pull/46935) by [@nishan](https://github.com/intergalacticspacehighway))
24
+
25
+ ## 56.0.17 — 2026-06-15
26
+
27
+ ### 🐛 Bug fixes
28
+
29
+ - [Android] Fixed Expo UI re-compose when switching screens in react-native-screens. ([#46650](https://github.com/expo/expo/pull/46650) by [@kudo](https://github.com/kudo))
30
+
13
31
  ## 56.0.16 — 2026-06-10
14
32
 
15
33
  ### 🎉 New features
@@ -19,6 +37,7 @@
19
37
  ### 🐛 Bug fixes
20
38
 
21
39
  - [android] Add a synchronous shadow node size update path, fixing a layout shift for `Host` `matchContents` views. ([#46604](https://github.com/expo/expo/pull/46604) by [@nishan](https://github.com/intergalacticspacehighway))
40
+ - [Android] Fix `NullPointerException` crash when a `matchContents` view is unmounted while a shadow node size update is pending (e.g. closing a bottom sheet mid-resize). ([#46785](https://github.com/expo/expo/pull/46785) by [@nishan](https://github.com/intergalacticspacehighway))
22
41
  - [iOS] Accept JS `Double` timestamps in `Date` Convertible. JS numbers arrive across the JSI bridge as Swift `Double`; the prior `as? Int` branch never matched, throwing `ConvertingException<Date>` whenever a JS caller passed `someDate.getTime()` to a `Date` / `Date?` argument. ([#46340](https://github.com/expo/expo/pull/46340) by [@kyleasaff](https://github.com/kyleasaff))
23
42
  - [Android] Fix events being silently dropped for Compose views in custom modules. ([#46623](https://github.com/expo/expo/issues/46623) by [@benjaminkomen](https://github.com/benjaminkomen)) ([#46624](https://github.com/expo/expo/pull/46624) by [@nishan](https://github.com/intergalacticspacehighway))
24
43
 
@@ -27,7 +27,7 @@ if (shouldIncludeCompose) {
27
27
  }
28
28
 
29
29
  group = 'host.exp.exponent'
30
- version = '56.0.16'
30
+ version = '56.0.18'
31
31
 
32
32
  def isExpoModulesCoreTests = {
33
33
  Gradle gradle = getGradle()
@@ -94,7 +94,7 @@ android {
94
94
  defaultConfig {
95
95
  consumerProguardFiles 'proguard-rules.pro'
96
96
  versionCode 1
97
- versionName "56.0.16"
97
+ versionName "56.0.18"
98
98
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
99
99
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
100
100
 
@@ -17,6 +17,10 @@ import androidx.annotation.UiThread
17
17
  import androidx.compose.ui.platform.ComposeView
18
18
  import androidx.compose.ui.platform.ViewCompositionStrategy
19
19
  import androidx.core.view.size
20
+ import androidx.lifecycle.LifecycleOwner
21
+ import androidx.lifecycle.setViewTreeLifecycleOwner
22
+ import androidx.savedstate.SavedStateRegistryOwner
23
+ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
20
24
  import expo.modules.kotlin.AppContext
21
25
  import expo.modules.kotlin.exception.CodedException
22
26
  import expo.modules.kotlin.types.enforceType
@@ -52,10 +56,14 @@ abstract class ExpoComposeView<T : ComposeProps>(
52
56
  context: Context,
53
57
  appContext: AppContext,
54
58
  private val withHostingView: Boolean = false
55
- ) : ExpoView(context, appContext) {
59
+ ) : ExpoView(context, appContext), ComposeHostingView {
56
60
  open val props: T? = null
57
61
  protected var recomposeScope: RecomposeScope? = null
58
62
 
63
+ // Retained so the composition can be disposed on unmount: its strategy is
64
+ // pinned to the Activity lifecycle, so nothing disposes it on window detach.
65
+ private var hostingComposeView: ComposeView? = null
66
+
59
67
  private val globalEvent = ViewEvent<Pair<String, Map<String, Any?>>>(GLOBAL_EVENT_NAME, this, null)
60
68
 
61
69
  /**
@@ -80,6 +88,9 @@ abstract class ExpoComposeView<T : ComposeProps>(
80
88
  }
81
89
 
82
90
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
91
+ if (shouldUseAndroidLayout && !isAttachedToWindow) {
92
+ return
93
+ }
83
94
  super.onLayout(changed, left, top, right, bottom)
84
95
 
85
96
  // Makes sure the child ComposeView is sticky with the current hosting view
@@ -191,22 +202,62 @@ abstract class ExpoComposeView<T : ComposeProps>(
191
202
 
192
203
  private fun addComposeView() {
193
204
  val composeView = ComposeView(context).also {
205
+ // Give each Host a unique id so its rememberSaveable state gets its own key.
206
+ // All Hosts share the Activity's SavedStateRegistry (set below), so without an id
207
+ // they'd collide on one key and only the first could save/restore state.
208
+ it.id = generateViewId()
194
209
  it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
195
- it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
210
+ // Pin the composition to the Activity lifecycle so it survives
211
+ // react-native-screens detaching inactive screens on every switch.
212
+ // The strategy alone isn't enough: Compose's WrappedComposition also
213
+ // observes the view-tree lifecycle owner found at first attach — the
214
+ // screen fragment's, which RN-screens destroys per switch — and
215
+ // self-disposes on its ON_DESTROY, leaving a dead composition that
216
+ // never recreates. Overriding the owners on the ComposeView (nearest
217
+ // tag wins) points both at the Activity. Unmount disposes explicitly
218
+ // via disposeHostedComposition().
219
+ val activity = appContext.currentActivity
220
+ if (activity is LifecycleOwner && activity is SavedStateRegistryOwner) {
221
+ it.setViewTreeLifecycleOwner(activity)
222
+ it.setViewTreeSavedStateRegistryOwner(activity)
223
+ it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(activity.lifecycle))
224
+ } else {
225
+ // No Activity to pin to: keep the prior behavior, including the
226
+ // dispose-on-reattach workaround for blank compositions after
227
+ // navigation (https://github.com/expo/expo/pull/34689).
228
+ it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
229
+ it.addOnAttachStateChangeListener(
230
+ OnAttachAfterDetachmentListener(onAttachAfterDetachment = {
231
+ it.disposeComposition()
232
+ })
233
+ )
234
+ }
196
235
  it.setContent {
197
236
  with(ComposableScope()) {
198
237
  Content()
199
238
  }
200
239
  }
201
- it.addOnAttachStateChangeListener(
202
- OnAttachAfterDetachmentListener(onAttachAfterDetachment = {
203
- it.disposeComposition()
204
- })
205
- )
206
240
  }
241
+ hostingComposeView = composeView
207
242
  addView(composeView)
208
243
  }
209
244
 
245
+ override fun disposeHostedComposition() {
246
+ hostingComposeView?.let {
247
+ // disposeComposition() alone leaves the composition strategy's lifecycle observer
248
+ // registered on the Activity, which leaks this view.
249
+ // Swapping the strategy first detaches that observer, then we dispose.
250
+ it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
251
+ // If the view is still attached when RN drops it, react-native-screens is keeping it
252
+ // on-screen for an in-progress navigation transition (e.g. a pop). Disposing now blanks
253
+ // the Compose content before the animation finishes (https://github.com/expo/expo/issues/47086).
254
+ // View eventually gets decomposed when RN screen detatches view from window
255
+ if (!it.isAttachedToWindow) {
256
+ it.disposeComposition()
257
+ }
258
+ }
259
+ }
260
+
210
261
  override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
211
262
  val view = if (child !is ExpoComposeView<*> && child !is ComposeView && this !is RNHostViewInterface) {
212
263
  ExpoComposeAndroidView(child, appContext)
@@ -58,6 +58,12 @@ std::shared_ptr<jsi::Object> JavaScriptObject::get() {
58
58
  return jsObject;
59
59
  }
60
60
 
61
+ jsi::Runtime &JavaScriptObject::getRuntime() {
62
+ auto runtime = runtimeHolder.lock();
63
+ assert((runtime != nullptr) && "JS Runtime was used after deallocation");
64
+ return runtime->get();
65
+ }
66
+
61
67
  bool JavaScriptObject::hasProperty(const std::string &name) {
62
68
  auto runtime = runtimeHolder.lock();
63
69
  assert((runtime != nullptr) && "JS Runtime was used after deallocation");
@@ -44,6 +44,11 @@ public:
44
44
 
45
45
  std::shared_ptr<jsi::Object> get() override;
46
46
 
47
+ /**
48
+ * @return the `jsi::Runtime` this object is bound to.
49
+ */
50
+ jsi::Runtime &getRuntime();
51
+
47
52
  /**
48
53
  * @return a bool whether the object has a property with the given name
49
54
  */
@@ -9,8 +9,8 @@ namespace expo {
9
9
  void NativeStatePropsGetter::registerNatives() {
10
10
  javaClassLocal()->registerNatives({
11
11
  makeNativeMethod("getStateProps", NativeStatePropsGetter::getStateProps),
12
- makeNativeMethod("updateStyleSizeImmediate", NativeStatePropsGetter::updateStyleSizeImmediate),
13
- makeNativeMethod("updateViewSizeImmediate", NativeStatePropsGetter::updateViewSizeImmediate),
12
+ makeNativeMethod("updateStyleSizeImmediateImpl", NativeStatePropsGetter::updateStyleSizeImmediate),
13
+ makeNativeMethod("updateViewSizeImmediateImpl", NativeStatePropsGetter::updateViewSizeImmediate),
14
14
  });
15
15
  }
16
16
 
@@ -3,6 +3,7 @@
3
3
  #if WORKLETS_ENABLED
4
4
 
5
5
  #include "../worklets/WorkletJSCallInvoker.h"
6
+ #include <worklets/Compat/StableApi.h>
6
7
  #include <worklets/WorkletRuntime/WorkletRuntime.h>
7
8
 
8
9
  #endif
@@ -15,7 +16,8 @@ namespace expo {
15
16
 
16
17
  void WorkletRuntimeInstaller::registerNatives() {
17
18
  javaClassLocal()->registerNatives({
18
- makeNativeMethod("install", WorkletRuntimeInstaller::install)
19
+ makeNativeMethod("install", WorkletRuntimeInstaller::install),
20
+ makeNativeMethod("resolveUIRuntimePointer", WorkletRuntimeInstaller::resolveUIRuntimePointer)
19
21
  });
20
22
  }
21
23
 
@@ -44,6 +46,28 @@ jni::local_ref<JSIContext::javaobject> WorkletRuntimeInstaller::install(
44
46
  #endif
45
47
  }
46
48
 
49
+ jlong WorkletRuntimeInstaller::resolveUIRuntimePointer(
50
+ jni::alias_ref<jni::JClass>,
51
+ jni::alias_ref<JavaScriptObject::javaobject> uiRuntimeHolder
52
+ ) noexcept {
53
+ #if WORKLETS_ENABLED
54
+ auto *holder = uiRuntimeHolder->cthis();
55
+ jsi::Runtime &runtime = holder->getRuntime();
56
+ std::shared_ptr<jsi::Object> holderObject = holder->get();
57
+
58
+ std::shared_ptr<worklets::WorkletRuntime> workletRuntime =
59
+ worklets::getWorkletRuntimeFromHolder(runtime, *holderObject);
60
+ if (!workletRuntime) {
61
+ return 0;
62
+ }
63
+
64
+ jsi::Runtime &uiRuntime = worklets::getJSIRuntimeFromWorkletRuntime(workletRuntime);
65
+ return reinterpret_cast<jlong>(&uiRuntime);
66
+ #else
67
+ return 0;
68
+ #endif
69
+ }
70
+
47
71
  void WorkletRuntimeInstaller::prepareRuntime(
48
72
  jni::local_ref<JSIContext::javaobject> jsiContext
49
73
  ) noexcept {
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include "../ExpoHeader.pch"
4
+ #include "../JavaScriptObject.h"
4
5
  #include "MainRuntimeInstaller.h"
5
6
 
6
7
  namespace jni = facebook::jni;
@@ -24,6 +25,14 @@ public:
24
25
  static void prepareRuntime(
25
26
  jni::local_ref<JSIContext::javaobject> jsiContext
26
27
  ) noexcept;
28
+
29
+ // Resolves the raw UI `jsi::Runtime *` (as a jlong) from a
30
+ // `react-native-worklets` UI runtime holder. Returns 0 when worklets isn't
31
+ // linked or the holder doesn't wrap a worklet runtime.
32
+ static jlong resolveUIRuntimePointer(
33
+ jni::alias_ref<jni::JClass> clazz,
34
+ jni::alias_ref<JavaScriptObject::javaobject> uiRuntimeHolder
35
+ ) noexcept;
27
36
  };
28
37
 
29
38
  } // namespace expo
@@ -6,6 +6,8 @@ import com.facebook.react.ReactActivity
6
6
  import expo.modules.BuildConfig
7
7
  import expo.modules.kotlin.events.normalizeEventName
8
8
  import expo.modules.kotlin.exception.Exceptions
9
+ import expo.modules.kotlin.jni.JavaScriptObject
10
+ import expo.modules.kotlin.jni.WorkletRuntimeInstaller
9
11
  import expo.modules.kotlin.modules.DEFAULT_MODULE_VIEW
10
12
  import expo.modules.kotlin.modules.Module
11
13
  import expo.modules.kotlin.modules.ModuleDefinition
@@ -95,26 +97,15 @@ class CoreModule : Module() {
95
97
  reactDelegate.reload()
96
98
  }
97
99
 
98
- Function("installOnUIRuntime") {
99
- val uiRuntimePointerHolder = appContext.runtime.jsiContext.global()["_WORKLET_RUNTIME"]
100
- ?: throw IllegalStateException("UI Runtime is not available. Make sure you have Reanimated installed and imported in your project.")
101
-
102
- assert(uiRuntimePointerHolder.isObject()) {
103
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer, got: ${uiRuntimePointerHolder.kind()}."
104
- }
105
-
106
- val uiRuntimePointerHolderObj = uiRuntimePointerHolder.getObject()
107
- assert(uiRuntimePointerHolderObj.isArrayBuffer()) {
108
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer, got: ${uiRuntimePointerHolder.kind()}."
100
+ Function("installOnUIRuntime") { uiRuntimeHolder: JavaScriptObject ->
101
+ val runtimePointer = WorkletRuntimeInstaller.resolveUIRuntimePointer(uiRuntimeHolder)
102
+ if (runtimePointer == 0L) {
103
+ throw IllegalStateException(
104
+ "Couldn't resolve the worklets UI runtime from the provided holder. " +
105
+ "Make sure `react-native-worklets` is installed and rebuild the app."
106
+ )
109
107
  }
110
108
 
111
- val arayBuffer = uiRuntimePointerHolderObj.getArrayBuffer()
112
- assert(arayBuffer.size() == 8) {
113
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer with a size of 8 bytes (a pointer size), got: ${arayBuffer.size()}."
114
- }
115
-
116
- val runtimePointer = arayBuffer.read8Byte(0)
117
-
118
109
  runBlocking {
119
110
  withContext(Dispatchers.Main) {
120
111
  appContext.uiRuntime.install(runtimePointer)
@@ -22,4 +22,15 @@ class WorkletRuntimeInstaller(
22
22
  jsRuntimePointer: Long,
23
23
  jniDeallocator: JNIDeallocator
24
24
  ): JSIContext
25
+
26
+ companion object {
27
+ /**
28
+ * Resolves the raw UI `jsi::Runtime*` (as a Long) from a `react-native-worklets`
29
+ * UI runtime holder. Returns 0 when worklets isn't installed or the holder
30
+ * doesn't wrap a worklet runtime.
31
+ */
32
+ @JvmStatic
33
+ @Suppress("KotlinJniMissingFunction")
34
+ external fun resolveUIRuntimePointer(uiRuntimeHolder: JavaScriptObject): Long
35
+ }
25
36
  }
@@ -1,5 +1,6 @@
1
1
  package expo.modules.kotlin.jni.fabric
2
2
 
3
+ import com.facebook.jni.HybridData
3
4
  import com.facebook.yoga.annotations.DoNotStrip
4
5
 
5
6
  @DoNotStrip
@@ -8,8 +9,31 @@ class NativeStatePropsGetter {
8
9
  external fun getStateProps(stateWrapper: Any): Map<String, Any?>?
9
10
 
10
11
  // Synchronously flush a style property size update in the current frame (pass NaN for "unset").
11
- external fun updateStyleSizeImmediate(stateWrapper: Any, styleWidth: Double, styleHeight: Double)
12
+ fun updateStyleSizeImmediate(stateWrapper: Any, styleWidth: Double, styleHeight: Double) {
13
+ if (!isStateValid(stateWrapper)) {
14
+ return
15
+ }
16
+ updateStyleSizeImmediateImpl(stateWrapper, styleWidth, styleHeight)
17
+ }
12
18
 
13
19
  // Synchronously flush a size update in the current frame.
14
- external fun updateViewSizeImmediate(stateWrapper: Any, width: Double, height: Double)
20
+ fun updateViewSizeImmediate(stateWrapper: Any, width: Double, height: Double) {
21
+ if (!isStateValid(stateWrapper)) {
22
+ return
23
+ }
24
+ updateViewSizeImmediateImpl(stateWrapper, width, height)
25
+ }
26
+
27
+ private external fun updateStyleSizeImmediateImpl(
28
+ stateWrapper: Any,
29
+ styleWidth: Double,
30
+ styleHeight: Double
31
+ )
32
+
33
+ private external fun updateViewSizeImmediateImpl(stateWrapper: Any, width: Double, height: Double)
34
+
35
+ // The only `StateWrapper` is RN's `StateWrapperImpl`, a fbjni `HybridData`. When the shadow node is
36
+ // destroyed its native pointer is reset and calling into it throws. Skip the update like RN does
37
+ // before its own native state accesses.
38
+ private fun isStateValid(stateWrapper: Any): Boolean = (stateWrapper as? HybridData)?.isValid == true
15
39
  }
@@ -148,6 +148,7 @@ class ViewManagerWrapperDelegate(
148
148
 
149
149
  fun onDestroy(view: View) {
150
150
  try {
151
+ (view as? ComposeHostingView)?.disposeHostedComposition()
151
152
  definition.onViewDestroys?.invoke(view)
152
153
  } catch (exception: Throwable) {
153
154
  // The view wasn't constructed correctly, so errors are expected.
@@ -177,3 +178,11 @@ class ViewManagerWrapperDelegate(
177
178
  }
178
179
  }
179
180
  }
181
+
182
+ /**
183
+ * Implemented by Compose-based views to dispose their composition when the
184
+ * view is destroyed.
185
+ */
186
+ interface ComposeHostingView {
187
+ fun disposeHostedComposition()
188
+ }
@@ -79,10 +79,11 @@ declare namespace ExpoGlobal {
79
79
  */
80
80
  export function reloadAppAsync(reason: string): Promise<void>;
81
81
  /**
82
- * Installs Expo Modules inside the Reanimated UI worklet runtime.
83
- * @platform android
82
+ * Installs Expo Modules inside the worklets UI runtime.
83
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
84
+ * `react-native-worklets`.
84
85
  */
85
- export function installOnUIRuntime(): void;
86
+ export function installOnUIRuntime(uiRuntimeHolder: object): void;
86
87
  }
87
88
  declare global {
88
89
  namespace NodeJS {
@@ -1 +1 @@
1
- {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/global.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,KAAK,UAAU,GAAG;IAChB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,aAAa,GAAG,YAAY,GAAG,MAAM,CAAC;IAChD,qCAAqC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,gKAAgK;IAChK,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,sKAAsK;IACtK,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,0LAA0L;IAC1L,gBAAgB,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAErD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,cAAc,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;GAGG;AACH,OAAO,WAAW,UAAU,CAAC;IAC3B,8DAA8D;IAC9D,MAAM,CAAC,IAAI,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAGxC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IAIxB;;;;OAIG;IACH,MAAM,CAAC,MAAM,sBAAsB,EAC/B,SAAS,GACT;QACE,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAEN;;;;OAIG;IACH,MAAM,CAAC,MAAM,QAAQ,EAAE,SAAS,GAAG,MAAM,CAAC;IAE1C;;;;OAIG;IACH,MAAM,CAAC,MAAM,YAAY,EAAE,SAAS,GAAG,MAAM,CAAC;IAI9C;;OAEG;IACH,MAAM,UAAU,MAAM,IAAI,MAAM,CAAC;IAEjC;;OAEG;IACH,MAAM,UAAU,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAEhE;;;OAGG;IACH,MAAM,UAAU,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAExF;;OAEG;IACH,MAAM,UAAU,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;OAGG;IACH,MAAM,UAAU,kBAAkB,IAAI,IAAI,CAAC;CAC5C;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM,CAAC;QACf,UAAiB,UAAW,SAAQ,cAAc;SAAG;QACrD,UAAiB,OAAQ,SAAQ,WAAW;YAC1C,GAAG,EAAE,UAAU,CAAC;SACjB;KACF;IAED,IAAI,IAAI,EAAE,OAAO,UAAU,CAAC;IAC5B,IAAI,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;IAE5B;;OAEG;IACH,IAAI,cAAc,EAAE,cAAc,CAAC;CACpC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACrD,YAAY,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/global.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,9 @@
1
- export declare function installOnUIRuntime(): void;
1
+ /**
2
+ * Installs Expo Modules on the UI worklet runtime so worklet callbacks and
3
+ * serializable SharedObjects work there.
4
+ *
5
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
6
+ * `react-native-worklets`.
7
+ */
8
+ export declare function installOnUIRuntime(uiRuntimeHolder: object): void;
2
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worklets/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,SASjC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worklets/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,QASzD"}
@@ -670,8 +670,13 @@ public final class AppContext: NSObject, EXAppContextProtocol, @unchecked Sendab
670
670
  if moduleRegistry.has(moduleWithName: "ExpoGo") {
671
671
  NotificationCenter.default.post(name: NSNotification.Name(rawValue: "EXReloadActiveAppRequest"), object: nil)
672
672
  } else {
673
- DispatchQueue.main.async {
674
- RCTTriggerReloadCommandListeners(reason)
673
+ // Must run on the main thread (see #31789), but synchronously when already there — deferring
674
+ // via `DispatchQueue.main.async` deadlocks the bridgeless reload against TurboModule teardown.
675
+ let trigger = { RCTTriggerReloadCommandListeners(reason) }
676
+ if Thread.isMainThread {
677
+ trigger()
678
+ } else {
679
+ DispatchQueue.main.async(execute: trigger)
675
680
  }
676
681
  }
677
682
  }
@@ -4,8 +4,6 @@ internal import React
4
4
  import Foundation
5
5
  import ExpoModulesJSI
6
6
 
7
- private let WORKLET_RUNTIME_KEY = "_WORKLET_RUNTIME"
8
-
9
7
  // The core module that describes the `global.expo` object.
10
8
  internal final class CoreModule: Module {
11
9
  internal func definition() -> ModuleDefinition {
@@ -29,7 +27,7 @@ internal final class CoreModule: Module {
29
27
  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.path ?? ""
30
28
  }
31
29
 
32
- Function("installOnUIRuntime") {
30
+ Function("installOnUIRuntime") { (uiRuntimeHolder: JavaScriptValue) in
33
31
  guard let appContext else {
34
32
  throw Exceptions.AppContextLost()
35
33
  }
@@ -40,12 +38,7 @@ internal final class CoreModule: Module {
40
38
  }
41
39
 
42
40
  let runtime = try appContext.runtime
43
- if !runtime.global().hasProperty(WORKLET_RUNTIME_KEY) {
44
- throw WorkletUIRuntimeException()
45
- }
46
-
47
- let pointerHolder = runtime.global().getProperty(WORKLET_RUNTIME_KEY)
48
- if !pointerHolder.isObject() {
41
+ guard uiRuntimeHolder.isObject() else {
49
42
  throw WorkletUIRuntimeException()
50
43
  }
51
44
 
@@ -61,7 +54,7 @@ internal final class CoreModule: Module {
61
54
 
62
55
  let block = {
63
56
  do {
64
- let uiRuntime = try factory(appContext, pointerHolder, runtime)
57
+ let uiRuntime = try factory(appContext, uiRuntimeHolder, runtime)
65
58
  appContext._uiRuntime = uiRuntime
66
59
  } catch {
67
60
  errorHolder.error = error
@@ -8,20 +8,13 @@ import ExpoModulesJSI
8
8
  @objc(EXWorkletIntegration)
9
9
  public final class WorkletIntegration: NSObject {
10
10
  @objc public static func register() {
11
- AppContext.uiRuntimeFactory = { _, pointerValue, runtime in
12
- // The worklet runtime pointer is passed as a JS ArrayBuffer containing the raw address.
13
- // This is an internal transport between the worklets package's own install code and this
14
- // factory we trust the ArrayBuffer bytes to be a valid `jsi::Runtime *`. No authentication
15
- // is performed; the size check below is only a sanity guard against malformed transport.
16
- guard pointerValue.isArrayBuffer() else {
17
- throw WorkletRuntimePointerExtractionException()
18
- }
19
- let arrayBuffer = pointerValue.getArrayBuffer()
20
- guard arrayBuffer.size == MemoryLayout<UnsafeMutableRawPointer>.size else {
21
- throw WorkletRuntimePointerExtractionException()
11
+ AppContext.uiRuntimeFactory = { _, holder, runtime in
12
+ let runtimePointer: UnsafeMutableRawPointer? = runtime.withUnsafePointee { runtimePointee in
13
+ holder.withUnsafePointee { holderPointee in
14
+ WorkletRuntimeResolver.uiRuntimePointer(runtimePointer: runtimePointee, holderPointer: holderPointee)
15
+ }
22
16
  }
23
- let pointer = UnsafeMutableRawPointer(arrayBuffer.data()).load(as: UnsafeMutableRawPointer.self)
24
- guard let workletRuntime = WorkletRuntime(runtimePointer: pointer) else {
17
+ guard let runtimePointer, let workletRuntime = WorkletRuntime(runtimePointer: runtimePointer) else {
25
18
  throw WorkletRuntimePointerExtractionException()
26
19
  }
27
20
  return workletRuntime
@@ -29,7 +22,7 @@ public final class WorkletIntegration: NSObject {
29
22
  }
30
23
  }
31
24
 
32
- private final class WorkletRuntimePointerExtractionException: Exception, @unchecked Sendable {
25
+ internal final class WorkletRuntimePointerExtractionException: Exception, @unchecked Sendable {
33
26
  override var reason: String {
34
27
  "Cannot extract pointer to UI worklet runtime"
35
28
  }
@@ -25,6 +25,10 @@ NS_SWIFT_NAME(WorkletsProvider)
25
25
  - (nullable id)workletRuntimeHandleForRawPointer:(void *)rawPointer
26
26
  NS_SWIFT_NAME(workletRuntimeHandle(rawPointer:));
27
27
 
28
+ - (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
29
+ holderPointer:(const void *)holderPointer
30
+ NS_SWIFT_NAME(uiRuntimePointer(runtimePointer:holderPointer:));
31
+
28
32
  - (void)scheduleWorkletWithRuntimeHandle:(id)runtimeHandle
29
33
  serializable:(EXJavaScriptSerializable *)serializable
30
34
  arguments:(NSArray *)arguments
@@ -0,0 +1,23 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ NS_SWIFT_NAME(WorkletRuntimeResolver)
8
+ @interface EXWorkletRuntimeResolver : NSObject
9
+
10
+ /**
11
+ Resolves the raw `jsi::Runtime *` of the UI worklet runtime from a
12
+ `react-native-worklets` UI runtime holder.
13
+ @param runtimePointer Raw pointer to the RN `jsi::Runtime`.
14
+ @param holderPointer Raw pointer to the holder `jsi::Value`.
15
+ Returns NULL when the runtime can't be resolved.
16
+ */
17
+ + (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
18
+ holderPointer:(const void *)holderPointer
19
+ NS_SWIFT_NAME(uiRuntimePointer(runtimePointer:holderPointer:));
20
+
21
+ @end
22
+
23
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,15 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ #import <ExpoModulesWorklets/WorkletRuntimeResolver.h>
4
+ #import <ExpoModulesWorklets/EXWorkletsProvider.h>
5
+
6
+ @implementation EXWorkletRuntimeResolver
7
+
8
+ + (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
9
+ holderPointer:(const void *)holderPointer
10
+ {
11
+ id<EXWorkletsProvider> provider = EXWorkletsProviderRegistry.shared;
12
+ return [provider uiRuntimePointerWithRuntimePointer:runtimePointer holderPointer:holderPointer];
13
+ }
14
+
15
+ @end
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include <memory>
9
9
  #include <vector>
10
+ #include <worklets/Compat/StableApi.h>
10
11
  #include <worklets/SharedItems/Serializable.h>
11
12
  #include <worklets/WorkletRuntime/WorkletRuntime.h>
12
13
 
@@ -163,6 +164,26 @@ static jsi::Value callWorklet(jsi::Runtime &rt, std::shared_ptr<worklets::Serial
163
164
  return handle;
164
165
  }
165
166
 
167
+ - (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
168
+ holderPointer:(const void *)holderPointer
169
+ {
170
+ jsi::Runtime &rt = *reinterpret_cast<jsi::Runtime *>(runtimePointer);
171
+ const jsi::Value &holderValue = *reinterpret_cast<const jsi::Value *>(holderPointer);
172
+
173
+ if (!holderValue.isObject()) {
174
+ return nullptr;
175
+ }
176
+
177
+ jsi::Object holder = holderValue.getObject(rt);
178
+ std::shared_ptr<worklets::WorkletRuntime> workletRuntime = worklets::getWorkletRuntimeFromHolder(rt, holder);
179
+ if (!workletRuntime) {
180
+ return nullptr;
181
+ }
182
+
183
+ jsi::Runtime &uiRuntime = worklets::getJSIRuntimeFromWorkletRuntime(workletRuntime);
184
+ return &uiRuntime;
185
+ }
186
+
166
187
  - (void)scheduleWorkletWithRuntimeHandle:(id)runtimeHandle
167
188
  serializable:(EXJavaScriptSerializable *)serializable
168
189
  arguments:(NSArray *)arguments
@@ -30,4 +30,24 @@ struct WorkletsNotInstalledTests {
30
30
  _ = try dynamicType.cast(jsValue: jsValue, appContext: appContext)
31
31
  }
32
32
  }
33
+
34
+ @Test
35
+ func `UI runtime install fails with an error (not a crash) when the worklets adapter is not linked`() throws {
36
+ // Only meaningful while the adapter is unlinked (the default in this test harness).
37
+ try #require(WorkletsProviderRegistry.shared == nil)
38
+
39
+ // Installs the core-side hook that turns a UI runtime holder into a runtime.
40
+ WorkletIntegration.register()
41
+ let factory = try #require(AppContext.uiRuntimeFactory)
42
+
43
+ let appContext = AppContext.create()
44
+ let runtime = try appContext.runtime
45
+ // With no adapter linked the resolver returns NULL for any holder, so the
46
+ // factory must throw rather than dereference a null runtime pointer.
47
+ let holder = try runtime.eval("({})")
48
+
49
+ #expect(throws: WorkletRuntimePointerExtractionException.self) {
50
+ _ = try factory(appContext, holder, runtime)
51
+ }
52
+ }
33
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "56.0.16",
3
+ "version": "56.0.18",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@expo/expo-modules-macros-plugin": "0.2.2",
50
- "expo-modules-jsi": "~56.0.9",
50
+ "expo-modules-jsi": "~56.0.10",
51
51
  "invariant": "^2.2.4"
52
52
  },
53
53
  "peerDependencies": {
@@ -66,7 +66,7 @@
66
66
  "@types/invariant": "^2.2.33",
67
67
  "expo-module-scripts": "56.0.3"
68
68
  },
69
- "gitHead": "b1e94a5c1c5b19472a42ca25752a3533699bc46a",
69
+ "gitHead": "b293744c7b199c6cbe910188863e93a174c21250",
70
70
  "scripts": {
71
71
  "build": "expo-module build",
72
72
  "clean": "expo-module clean",
@@ -27,7 +27,7 @@ export function installExpoGlobalPolyfill() {
27
27
  cacheDir: undefined,
28
28
  documentsDir: undefined,
29
29
 
30
- installOnUIRuntime: () => {
30
+ installOnUIRuntime: (_uiRuntimeHolder: object) => {
31
31
  throw new Error('Method not implemented.');
32
32
  },
33
33
  };
@@ -102,10 +102,11 @@ declare namespace ExpoGlobal {
102
102
  export function reloadAppAsync(reason: string): Promise<void>;
103
103
 
104
104
  /**
105
- * Installs Expo Modules inside the Reanimated UI worklet runtime.
106
- * @platform android
105
+ * Installs Expo Modules inside the worklets UI runtime.
106
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
107
+ * `react-native-worklets`.
107
108
  */
108
- export function installOnUIRuntime(): void;
109
+ export function installOnUIRuntime(uiRuntimeHolder: object): void;
109
110
  }
110
111
 
111
112
  /* eslint-disable no-var */
@@ -1,10 +1,17 @@
1
- export function installOnUIRuntime() {
1
+ /**
2
+ * Installs Expo Modules on the UI worklet runtime so worklet callbacks and
3
+ * serializable SharedObjects work there.
4
+ *
5
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
6
+ * `react-native-worklets`.
7
+ */
8
+ export function installOnUIRuntime(uiRuntimeHolder: object) {
2
9
  const installOnUIRuntimeNative = globalThis?.expo?.installOnUIRuntime;
3
10
  if (!installOnUIRuntimeNative) {
4
11
  throw Error(
5
- "Native method to install Expo Modules on Reanimated UI worklet runtime wasn't found in `expo-modules-core`."
12
+ "Native method to install Expo Modules on the worklets UI runtime wasn't found in `expo-modules-core`."
6
13
  );
7
14
  }
8
15
 
9
- installOnUIRuntimeNative();
16
+ installOnUIRuntimeNative(uiRuntimeHolder);
10
17
  }