Skip to content

[Expo] Android native UserButton becomes inert after navigation detach #9142

Description

@D3OXY

Preliminary checks

  • I reviewed the Clerk documentation.
  • I searched open and closed issues and PRs in clerk/javascript and other Clerk repositories.
  • I have not contacted Clerk support or Discord about this.
  • This is a reproducible SDK bug, not a support request.

Reproduction

Start from Clerk's official NativeComponentQuickstart, render the native UserButton inside an Expo Router/React Navigation header, and add any second route:

import { UserButton } from '@clerk/expo/native';
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack screenOptions={{ headerRight: () => <UserButton /> }}>
      <Stack.Screen name="index" />
      <Stack.Screen name="details" />
    </Stack>
  );
}

Use any Clerk development instance and a signed-in user with a profile image.

Description

On Android, the native Expo UserButton stops rendering live content and stops responding after its header is detached and reattached by navigation. It works correctly on the first render. After navigating to another route and returning, it shows the generic fallback avatar instead of the user's profile image. Tapping it no longer opens the account UI.

iOS is unaffected.

Steps to reproduce

  1. Launch the Android development build while signed in.
  2. Confirm the native UserButton shows the user's profile image and opens the Account UI.
  3. Navigate to any other route.
  4. Navigate back, or otherwise return to a route whose header contains the same UserButton.
  5. Observe the generic avatar and tap it.

Expected behavior

The profile image remains hydrated and tapping the button opens the native Account UI after any number of detach/reattach navigation cycles.

Actual behavior

The native Compose content becomes inert after its first detach. The avatar falls back to the generic image and tapping it does nothing for the remainder of the process.

Root cause

ClerkComposeNativeViewHost creates its Recomposer only once, during host construction:

https://github.com/clerk/javascript/blob/main/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt#L29-L46

It then permanently cancels that recomposer and its job on every temporary window detach:

https://github.com/clerk/javascript/blob/main/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt#L48-L53

On reattach, ClerkUserButtonNativeView calls setupView(), but the ComposeView still points at the canceled parent recomposer:

https://github.com/clerk/javascript/blob/main/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt#L20-L23

React Navigation header transitions routinely detach and reattach native views, so this is a normal lifecycle event rather than destruction. No exception is emitted; the composition simply no longer updates or handles input.

The iOS host explicitly tears down its hosting controller, clears hasInitialized, and creates a new hosted controller when it returns to a window, which explains the platform difference:

https://github.com/clerk/javascript/blob/main/packages/expo/ios/ClerkNativeViewHost.swift#L22-L40

Verified fix

I verified a local patch that:

  • disposes the existing composition on detach;
  • cancels and clears the old recomposer/job;
  • creates and assigns a new parent recomposer in onAttachedToWindow() before the subclass calls setupView().

Validation on an Android API 36 emulator:

  • cold start: profile image rendered and Account UI opened;
  • details route → back: profile image remained and Account UI opened;
  • AI Chat tab → Home tab: profile image remained and Account UI opened;
  • repeated detach/reattach cycles: Account UI continued opening;
  • Android debug Kotlin build succeeded;
  • no recomposer, Compose lifecycle, IllegalStateException, or fatal runtime errors were logged.

I will link a PR with the patch and regression coverage.

Environment

System:
  OS: macOS 26.5.2
  CPU: (10) arm64 Apple M4
Binaries:
  Node: 24.13.1
  pnpm: 11.7.0
npmPackages:
  @clerk/expo: 3.7.0
  expo: 57.0.4
  expo-router: 57.0.4
  react: 19.2.3
  react-native: 0.86.0
Device:
  Android API 36 arm64 emulator

Publishable key omitted because the failure reproduces with any signed-in development instance and does not depend on instance configuration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions