From fbfa3498828db32d405dde83b6cfc8a83d85eb91 Mon Sep 17 00:00:00 2001 From: d3oxy Date: Mon, 13 Jul 2026 05:30:36 +0530 Subject: [PATCH] fix(expo): recreate Android recomposer after reattach --- .changeset/fuzzy-lions-recompose.md | 5 +++ .../clerk/ClerkComposeNativeViewHost.kt | 34 +++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .changeset/fuzzy-lions-recompose.md diff --git a/.changeset/fuzzy-lions-recompose.md b/.changeset/fuzzy-lions-recompose.md new file mode 100644 index 00000000000..af0429726e6 --- /dev/null +++ b/.changeset/fuzzy-lions-recompose.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo': patch +--- + +Fix Android native UI components becoming unresponsive after their views detach and reattach during navigation. diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt index 67d5f6d681c..ff85acaf4c4 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt @@ -31,27 +31,41 @@ abstract class ClerkComposeNativeViewHost(context: Context, appContext: AppConte view.setViewTreeLifecycleOwner(act) view.setViewTreeViewModelStoreOwner(act) view.setViewTreeSavedStateRegistryOwner(act) - - val recomposerContext = AndroidUiDispatcher.Main - val newRecomposer = Recomposer(recomposerContext) - recomposer = newRecomposer - view.setParentCompositionContext(newRecomposer) - val scope = CoroutineScope(recomposerContext + kotlinx.coroutines.SupervisorJob()) - recomposerJob = scope.coroutineContext[kotlinx.coroutines.Job] - scope.launch { - newRecomposer.runRecomposeAndApplyChanges() - } } addView(view, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + startRecomposer() + } + override fun onDetachedFromWindow() { + composeView.disposeComposition() recomposer?.cancel() recomposerJob?.cancel() + recomposer = null + recomposerJob = null onHostDetachedFromWindow() super.onDetachedFromWindow() } + private fun startRecomposer() { + if (activity == null || recomposerJob?.isActive == true) return + + // Navigation can temporarily detach and later reattach the same native host. + // Always give a reattached ComposeView a live parent composition context. + val recomposerContext = AndroidUiDispatcher.Main + val newRecomposer = Recomposer(recomposerContext) + recomposer = newRecomposer + composeView.setParentCompositionContext(newRecomposer) + val scope = CoroutineScope(recomposerContext + kotlinx.coroutines.SupervisorJob()) + recomposerJob = scope.coroutineContext[kotlinx.coroutines.Job] + scope.launch { + newRecomposer.runRecomposeAndApplyChanges() + } + } + fun setupView() { composeView.setContent { val viewModelStoreOwner = localViewModelStoreOwner()