diff --git a/.changeset/fuzzy-lions-recompose.md b/.changeset/fuzzy-lions-recompose.md new file mode 100644 index 00000000000..732d982b6e2 --- /dev/null +++ b/.changeset/fuzzy-lions-recompose.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo': patch +--- + +Fix Android native UI components crashing before window attachment or becoming unresponsive after navigation detaches and reattaches their views. 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..9ec522262b8 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 @@ -18,41 +18,57 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner import expo.modules.kotlin.AppContext import expo.modules.kotlin.views.ExpoView import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch abstract class ClerkComposeNativeViewHost(context: Context, appContext: AppContext) : ExpoView(context, appContext) { protected val activity: ComponentActivity? = findActivity(context) private var recomposer: Recomposer? = null - private var recomposerJob: kotlinx.coroutines.Job? = null + private var recomposerJob: Job? = null private val composeView = ComposeView(context).also { view -> activity?.let { act -> 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() + setupView() + } + 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 + SupervisorJob()) + recomposerJob = scope.launch { + newRecomposer.runRecomposeAndApplyChanges() + } + } + fun setupView() { + startRecomposer() composeView.setContent { val viewModelStoreOwner = localViewModelStoreOwner() diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt index 6ad219d65fc..68ca3c5184a 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt @@ -17,11 +17,6 @@ class ClerkUserButtonNativeView(context: Context, appContext: AppContext) : Cler activity?.let { Clerk.attachActivity(it) } } - override fun onAttachedToWindow() { - super.onAttachedToWindow() - setupView() - } - @Composable override fun Content() { Box(