一键登录 - Activity 使用 Jetpack Compose 的结果

One Tap Sign in - Activity Result with Jetpack Compose

我正在尝试将使用 Google 的“一键登录”功能集成到我使用 Jetpack Compose 构建的应用程序中。我正在使用 startIntentSenderForResult 来启动一个意图,但现在的问题是我无法从我的可组合函数接收 activity 结果。我正在使用 rememberLauncherForActivityResult 从意图中获取结果,但仍然没有得到任何结果。有什么解决办法吗?

登录屏幕

@Composable
fun LoginScreen() {
    val activity = LocalContext.current as Activity

    val activityResult = remember { mutableStateOf<ActivityResult?>(null) }
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        val oneTapClient = Identity.getSignInClient(activity)
        val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
        val idToken = credential.googleIdToken
        if (idToken != null) {
            // Got an ID token from Google. Use it to authenticate
            // with your backend.
            Log.d("LOG", idToken)
        } else {
            Log.d("LOG", "Null Token")
        }

        Log.d("LOG", "ActivityResult")
        if (result.resultCode == Activity.RESULT_OK) {
            activityResult.value = result
        }
    }

    activityResult.value?.let { _ ->
        Log.d("LOG", "ActivityResultValue")
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        GoogleButton(
            onClick = {
                signIn(
                    activity = activity
                )
            }
        )
    }
}

fun signIn(
    activity: Activity
) {
    val oneTapClient = Identity.getSignInClient(activity)
    val signInRequest = BeginSignInRequest.builder()
        .setGoogleIdTokenRequestOptions(
            BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
                .setSupported(true)
                // Your server's client ID, not your Android client ID.
                .setServerClientId(CLIENT_ID)
                // Only show accounts previously used to sign in.
                .setFilterByAuthorizedAccounts(true)
                .build()
        )
        // Automatically sign in when exactly one credential is retrieved.
        .setAutoSelectEnabled(true)
        .build()

    oneTapClient.beginSignIn(signInRequest)
        .addOnSuccessListener(activity) { result ->
            try {
                startIntentSenderForResult(
                    activity, result.pendingIntent.intentSender, ONE_TAP_REQ_CODE,
                    null, 0, 0, 0, null
                )
            } catch (e: IntentSender.SendIntentException) {
                Log.e("LOG", "Couldn't start One Tap UI: ${e.localizedMessage}")
            }
        }
        .addOnFailureListener(activity) { e ->
            // No saved credentials found. Launch the One Tap sign-up flow, or
            // do nothing and continue presenting the signed-out UI.
            Log.d("LOG", e.message.toString())
        }
}

你实际上并没有在你创建的 launcher 上调用 launch,所以你永远不会在那里得到结果。

而不是使用 StartActivityForResult 合同,您需要使用 StartIntentSenderForResult contract - 这是一个需要 IntentSender 的合同,就像您从 beginSignIn方法。

这意味着您的代码应该如下所示:

@Composable
fun LoginScreen() {
    val context = LocalContext.current

    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()
    ) { result ->
        if (result.resultCode != Activity.RESULT_OK) {
          // The user cancelled the login, was it due to an Exception?
          if (result.data?.action == StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST) {
            val exception: Exception? = result.data?.getSerializableExtra(StartIntentSenderForResult.EXTRA_SEND_INTENT_EXCEPTION)
            Log.e("LOG", "Couldn't start One Tap UI: ${e?.localizedMessage}")
          }
          return@rememberLauncherForActivityResult
        }
        val oneTapClient = Identity.getSignInClient(context)
        val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
        val idToken = credential.googleIdToken
        if (idToken != null) {
            // Got an ID token from Google. Use it to authenticate
            // with your backend.
            Log.d("LOG", idToken)
        } else {
            Log.d("LOG", "Null Token")
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // Create a scope that is automatically cancelled
        // if the user closes your app while async work is
        // happening
        val scope = rememberCoroutineScope()
        GoogleButton(
            onClick = {
                scope.launch {
                  signIn(
                    context = context,
                    launcher = launcher
                  )
                }
            }
        )
    }
}

suspend fun signIn(
    context: Context,
    launcher: ActivityResultLauncher<IntentSenderRequest>
) {
    val oneTapClient = Identity.getSignInClient(context)
    val signInRequest = BeginSignInRequest.builder()
        .setGoogleIdTokenRequestOptions(
            BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
                .setSupported(true)
                // Your server's client ID, not your Android client ID.
                .setServerClientId(CLIENT_ID)
                // Only show accounts previously used to sign in.
                .setFilterByAuthorizedAccounts(true)
                .build()
        )
        // Automatically sign in when exactly one credential is retrieved.
        .setAutoSelectEnabled(true)
        .build()

    try {
        // Use await() from https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services
        // Instead of listeners that aren't cleaned up automatically
        val result = oneTapClient.beginSignIn(signInRequest).await()

        // Now construct the IntentSenderRequest the launcher requires
        val intentSenderRequest = IntentSenderRequest.Builder(result.pendingIntent).build()
        launcher.launch(intentSenderRequest)
    } catch (e: Exception) {
        // No saved credentials found. Launch the One Tap sign-up flow, or
        // do nothing and continue presenting the signed-out UI.
        Log.d("LOG", e.message.toString())
    }
}