我正在使用 Kotlin、Android Studio 和 Jetpack Compose
我想做的事:
单击按钮后,我想使用谷歌身份验证和 firebase 注册用户
我尝试过的事情:
我尝试遵循 Firebase Auth Documentation,但它真的很难理解,因为它经常让我回到 Google 文档,这对于 Jetpack Compose 来说也没有帮助。
我找不到任何解释此实现的最新视频或指南。如果有人能解释如何开始,那就太棒了。
val context = LocalContext.current
val token = stringResource(R.string.default_web_client_id)
val launcherNav = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
navController.navigate(Screen.MainScreen.route)
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) {
val task =
try {
val account = GoogleSignIn.getSignedInAccountFromIntent(it.data)
.getResult(ApiException::class.java)
val credential = GoogleAuthProvider.getCredential(account.idToken!!, null)
FirebaseAuth.getInstance().signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
}
}
}
catch (e: ApiException) {
Log.w("TAG", "GoogleSign in Failed", e)
}
}
Button(
onClick = {
val gso = GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(token)
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(context, gso)
launcher.launch(googleSignInClient.signInIntent)
}
) {
Text(text = "Sign In")
}
所以我使用的文档是 使用 Google 登录对用户进行身份验证
根据此文档和 Firebase 文档,我能够找到解决方案。此解决方案使用 Credential Manager 以及 Firebase。
为日志消息创建标签
val TAG = "FirebaseGoogleSignin"
首先创建凭证管理器的实例以及上下文
val activityContext = this as Context
val credentialManager = CredentialManager.create(activityContext)
创建使用 Google 登录请求。
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(WEB_CLIENT_ID)
.setNonce(generateNonce())
.build()
使用它来创建获取凭据的请求
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
现在创建一个处理程序,它将处理收到的凭据。
fun handleSignIn(result: GetCredentialResponse) {
// Handle the successfully returned credential.
val credential = result.credential
when (credential) {
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
auth = Firebase.auth
// Use googleIdTokenCredential and extract id to validate and
// authenticate on your server. In our case the server is the firebase app
val googleIdTokenCredential = GoogleIdTokenCredential
.createFrom(credential.data)
val firebaseCredential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
// Sign into firebase with the acquired credentials
auth.signInWithCredential(firebaseCredential).addOnCompleteListener(
{ task ->
if (task.isSuccessful) {
// Login successful
Log.i(TAG, "Successfully logged in")
}
})
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e)
}
}
else {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
}
一旦所有这些准备就绪,我们就创建点击监听器。单击后,必须执行请求并且必须开始登录流程
Button(onClick= {
lifecycleScope.launch {
try {
val result = credentialManager.getCredential(
request = request,
context = activityContext,
)
handleSignIn(result)
} catch (e: GetCredentialException) {
Log.e(TAG, e.toString())
}
}
}) {
Text(text="Sign in to Google")
}
这是我使用一些基本 UI 创建的整个活动
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.exceptions.GetCredentialException
import androidx.lifecycle.lifecycleScope
import com.cr7.budgetapp.ui.theme.BudgetAppTheme
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import com.google.firebase.Firebase
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.auth
import kotlinx.coroutines.launch
import java.security.SecureRandom
import java.util.Base64
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
class SignInActivity : ComponentActivity() {
private lateinit var auth: FirebaseAuth
// On credential recieve handler
fun handleSignIn(result: GetCredentialResponse, updateClicked: (Boolean) -> Unit) {
// Handle the successfully returned credential.
val credential = result.credential
when (credential) {
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
auth = Firebase.auth
// Use googleIdTokenCredential and extract id to validate and
// authenticate on your server.
val googleIdTokenCredential = GoogleIdTokenCredential
.createFrom(credential.data)
val firebaseCredential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
// Sign into firebase with the acquired credentials
auth.signInWithCredential(firebaseCredential).addOnCompleteListener(
{ task ->
if (task.isSuccessful) {
Log.i(TAG, "Successfully logged in")
updateClicked(false)
}
})
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e)
updateClicked(false)
}
}
else {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
updateClicked(false)
}
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
}
fun generateNonce(length: Int = 32): String {
val nonce = ByteArray(length)
SecureRandom().nextBytes(nonce)
return Base64.getUrlEncoder().withoutPadding().encodeToString(nonce)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val activityContext = this as Context
val credentialManager = CredentialManager.create(this)
// Creating a google sign in request
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(WEB_CLIENT_ID)
.setNonce(generateNonce())
.build()
// Create a request to get credentials
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
setContent {
var clicked by remember { mutableStateOf(false) }
Screen(clicked = clicked, updateClicked = {clicked = it}) {
lifecycleScope.launch {
try {
val result = credentialManager.getCredential(
request = request,
context = activityContext,
)
handleSignIn(result) { clicked = it }
} catch (e: GetCredentialException) {
Log.e(TAG, e.toString())
clicked = false
}
}
}
}
}
}
@Composable
fun Screen(clicked: Boolean, updateClicked: (Boolean) -> Unit, onClicked: () -> Unit) {
BudgetAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text(
text = "Hello Android !",
)
GoogleButton(updateClicked = {updateClicked(it)},clicked = clicked, loadingText = "Signing In to your account...") {
onClicked()
}
}
}
}
@Preview(showBackground = true, apiLevel = 33)
@Composable
fun PreviewScreen() {
Screen(clicked = false, updateClicked = {}) {
}
}
@Composable
fun GoogleButton(
modifier: Modifier = Modifier,
text: String = "Sign Up with Google",
loadingText: String = "Creating Account...",
icon: Int = R.drawable.ic_google_logo,
shape: Shape = MaterialTheme.shapes.medium,
borderColor: Color = Color.LightGray,
backgroundColor: Color = MaterialTheme.colorScheme.surface,
progressIndicatorColor: Color = MaterialTheme.colorScheme.primary,
clicked: Boolean,
updateClicked: (Boolean) -> Unit,
onClicked: () -> Unit,
) {
Surface(
modifier = modifier
.clip(MaterialTheme.shapes.medium)
.clickable {
updateClicked(!clicked)
onClicked()
},
shape = shape,
border = BorderStroke(width = 1.dp, color = borderColor),
color = backgroundColor
) {
Row(
modifier = Modifier
.padding(
start = 12.dp,
end = 16.dp,
top = 12.dp,
bottom = 12.dp
)
.animateContentSize(
animationSpec = tween(
durationMillis = 300,
easing = LinearOutSlowInEasing
)
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
painter = painterResource(id = icon),
contentDescription = "Google Button",
tint = Color.Unspecified
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = if (clicked) loadingText else text)
if (clicked) {
Spacer(modifier = Modifier.width(16.dp))
CircularProgressIndicator(
modifier = Modifier
.height(16.dp)
.width(16.dp),
strokeWidth = 2.dp,
color = progressIndicatorColor
)
}
}
}
}