I'm trying to integrate One Tap Sign in with Google into my app which I'm building with Jetpack Compose. I'm using startIntentSenderForResult to launch an intent, but now the problem is that I'm unable to receive activity result from my composable function. I'm using rememberLauncherForActivityResult to get the result from an intent but still not getting anywhere. Any solutions?
LoginScreen
@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())
}
}
CodePudding user response:
You aren't actually calling launch
on the launcher
you create, so you would never get a result back there.
Instead of using the StartActivityForResult
contract, you need to use the StartIntentSenderForResult
contract - that's the one that takes an IntentSender
like the one you get back from your beginSignIn
method.
This means your code should look like:
@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
}
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())
}
}