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