From 5aecdf251e430748a3f22683391943fab2ef60fc Mon Sep 17 00:00:00 2001 From: ChrisCanin Date: Tue, 5 May 2026 16:03:34 -0700 Subject: [PATCH] fix(expo): seed Clerk activity ref on cold-start to fix MissingActivity (MOBILE-485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clerk-android tracks the current foreground Activity via ActivityLifecycleCallbacks registered inside Clerk.initialize(). In a React Native app, MainActivity has already passed onResume() by the time mounts and configure() runs, so the callbacks miss the initial Activity. Without seeding, the first Credential Manager call (Google sign-in, passkeys) fails with MissingActivity until the user backgrounds and foregrounds the app. Adopt the new public Clerk.attachActivity() API from clerk-android 1.0.16 to seed the Activity reference at two reliable points: in ClerkExpoModule.configure() right after Clerk.initialize() (using React's currentActivity), and in ClerkAuthNativeView's findActivity() result. The second hook is the empirically-reliable backstop — at cold start, getCurrentActivity() may return null before React's host-resume sync, but ClerkAuthNativeView is constructed when AuthView mounts, by which point the Activity is unambiguously available. Bumps clerk-android-{api,ui} to 1.0.16. Adds an explicit clerk-android-api dep to override telemetry's transitive pin on the previous release — without this the version constraint loses the conflict and consumers end up on the older artifact (which lacks attachActivity). --- .changeset/expo-mobile-485-cold-start-activity.md | 7 +++++++ packages/expo/android/build.gradle | 14 ++++++++++++-- .../java/expo/modules/clerk/ClerkAuthExpoView.kt | 9 ++++++++- .../java/expo/modules/clerk/ClerkExpoModule.kt | 10 ++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .changeset/expo-mobile-485-cold-start-activity.md diff --git a/.changeset/expo-mobile-485-cold-start-activity.md b/.changeset/expo-mobile-485-cold-start-activity.md new file mode 100644 index 00000000000..554e999358d --- /dev/null +++ b/.changeset/expo-mobile-485-cold-start-activity.md @@ -0,0 +1,7 @@ +--- +"@clerk/expo": patch +--- + +Fix `MissingActivity` error on cold-start Google sign-in / passkey flows. Previously, the first tap on "Sign in with Google" in `` failed with `Clerk error: Google sign-in cannot start: Credential Manager requires an active Activity context.` — the workaround was to background and foreground the app once before signing in. + +The Android bridge now calls `Clerk.attachActivity()` (added in clerk-android 1.0.16) at SDK initialization and on AuthView/UserProfile mount, so the current Activity is registered with the underlying SDK before any Credential Manager call. No app-side changes required; the fix is transparent on rebuild. diff --git a/packages/expo/android/build.gradle b/packages/expo/android/build.gradle index bb501a1aa93..d860cd02919 100644 --- a/packages/expo/android/build.gradle +++ b/packages/expo/android/build.gradle @@ -18,8 +18,8 @@ ext { credentialsVersion = "1.3.0" googleIdVersion = "1.1.1" kotlinxCoroutinesVersion = "1.7.3" - clerkAndroidApiVersion = "1.0.13" - clerkAndroidUiVersion = "1.0.13" + clerkAndroidApiVersion = "1.0.16" + clerkAndroidUiVersion = "1.0.16" composeVersion = "1.7.0" activityComposeVersion = "1.9.0" lifecycleVersion = "2.8.0" @@ -117,6 +117,16 @@ dependencies { exclude group: 'com.squareup.okhttp3', module: 'okhttp' exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection' } + // clerk-android-telemetry has a transitive dep on the last released + // clerk-android-api. Pinning the api explicitly here keeps consumers + // compiling against the same version we ship the UI from. + implementation("com.clerk:clerk-android-api:$clerkAndroidApiVersion") { + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib' + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7' + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' + exclude group: 'com.squareup.okhttp3', module: 'okhttp' + exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection' + } // Jetpack Compose for wrapping Clerk views implementation "androidx.compose.ui:ui:$composeVersion" diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt index 8d3b1ed0100..ce948f7a8a4 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt @@ -44,7 +44,14 @@ class ClerkAuthNativeView(context: Context) : FrameLayout(context) { var mode: String = "signInOrUp" var isDismissable: Boolean = true - private val activity: ComponentActivity? = findActivity(context) + private val activity: ComponentActivity? = findActivity(context).also { + // At cold start, ClerkExpoModule.configure() may run before React's + // host-resume sync — meaning getCurrentActivity() returns null there. + // This view's construction is a reliable second hook: we know the Activity + // is available (we just walked the context to find it) and we're about to + // render Google sign-in / passkey buttons that need it. + if (it != null) Clerk.attachActivity(it) + } // Per-view ViewModelStoreOwner so the AuthView's ViewModels (including its // navigation state) are scoped to THIS view instance, not the activity. diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt index 1ab29a4ab0f..7c822cfda40 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt @@ -85,6 +85,16 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) : } Clerk.initialize(reactApplicationContext, pubKey) + // clerk-android registers ActivityLifecycleCallbacks during + // initialize(), but in React Native MainActivity has already passed + // onResume() by the time mounts and we reach this + // line, so the callbacks miss the initial activity. Without seeding, + // the first Credential Manager call (Google sign-in / passkeys) + // fails with MissingActivity until the user backgrounds and + // foregrounds the app. getCurrentActivity() can be null here on + // cold start before React's host-resume sync — AuthView and + // UserProfile also call attachActivity() on mount as a backstop. + getCurrentActivity()?.let { Clerk.attachActivity(it) } // Theme loading is centralized here. ClerkViewFactory.configure() // and ClerkUserProfileActivity.onCreate() only call Clerk.initialize() // when Clerk is not yet initialized, so by the time they run