У меня возникла проблема с реализацией входа в Google в приложении Android. У меня есть два фрагмента кода: первый работает правильно, а второй дает сбой во время вызова credentialManager.getCredential(context = context, request = request).
Первый код (рабочий)
fun NavGraphBuilder.loginComposable(
onNavigateToGenderAndAgeGroup: (Map) -> Unit,
onNavigateToQuote: () -> Unit
) {
composable(route = "login") {
val loginViewModel: LoginViewModel = hiltViewModel()
val pendingUserMap = loginViewModel.pendingUserMap.collectAsState().value
LoginScreen(
loginViewModel = loginViewModel,
onNavigateToGenderAndAgeGroup = {
pendingUserMap?.let { onNavigateToGenderAndAgeGroup(it) }
},
onNavigateToQuote = onNavigateToQuote
)
}
}
----------------
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideContext(@ApplicationContext context: Context): Context {
return context
}
@Provides
@Singleton
fun provideClientId(@ApplicationContext context: Context): String {
return context.getString(R.string.client_id)
}
@Provides
@Singleton
fun provideCredentialManager(@ApplicationContext context: Context): CredentialManager {
return CredentialManager.create(context)
}
}
--------------------
@Composable
fun LoginScreen(
loginViewModel: LoginViewModel,
onNavigateToGenderAndAgeGroup: () -> Unit,
onNavigateToQuote: () -> Unit
) {
val context = LocalContext.current
val authState by loginViewModel.authState.collectAsState()
when (authState) {
is AuthState.Loading -> {
CircularProgressIndicator(modifier = Modifier.fillMaxSize())
}
is AuthState.SignedOut -> {
LoginScreenContent(onGoogleLoginClick = {
loginViewModel.onGoogleLoginClicked()
})
}
is AuthState.SignedIn -> {
LaunchedEffect(Unit) {
onNavigateToQuote()
}
}
is AuthState.SignUpRequired -> {
LaunchedEffect(Unit) {
onNavigateToGenderAndAgeGroup()
}
}
is AuthState.Error -> {
val errorMessage = (authState as AuthState.Error).message
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
}
}
}
@Composable
fun LoginScreenContent(
onGoogleLoginClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(BlackBackground),
) {
Column(
modifier = Modifier
.offset(x = 34.dp, y = 99.dp)
.align(Alignment.TopStart),
) {
}
Column(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 80.dp),
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
horizontalAlignment = Alignment.Start,
) {
GoogleLoginButton(onGoogleLoginClick)
}
}
}
@Composable
private fun GoogleLoginButton(
onGoogleLoginClick: () -> Unit
) {
Row(
modifier = Modifier
.width(345.dp)
.height(54.dp)
.background(color = GrayScaleWhite, shape = RoundedCornerShape(size = 10.dp))
.padding(start = 17.dp)
.clickable(onClick = onGoogleLoginClick),
horizontalArrangement = Arrangement.spacedBy(79.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier
.width(24.dp)
.height(24.dp)
.background(color = GrayScaleWhite),
painter = painterResource(id = R.drawable.google_icon),
contentDescription = "google_icon",
contentScale = ContentScale.None,
)
Text(
text = stringResource(id = R.string.google_login),
style = TextStyle(
fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.inter_18pt_regular)),
fontWeight = FontWeight(400),
color = Color(0xFF000000),
),
)
}
}
---------------------------
@HiltViewModel
class LoginViewModel @Inject constructor(
private val credentialManager: CredentialManager,
private val clientId: String,
@ApplicationContext private val applicationContext: Context // 애플리케이션 컨텍스트 추가
) : ViewModel() {
private val tag = "LoginViewModel: "
private val firebaseAuth: FirebaseAuth = Firebase.auth
private val firestore: FirebaseFirestore = Firebase.firestore
private val _authState = MutableStateFlow(AuthState.SignedOut)
val authState: StateFlow = _authState.asStateFlow()
private val _pendingUserMap = MutableStateFlow(null)
val pendingUserMap: StateFlow = _pendingUserMap.asStateFlow()
init {
checkInitialAuthState()
}
private fun checkInitialAuthState() {
viewModelScope.launch {
_authState.value = if (isSignedIn()) {
val currentUser = getCurrentUser()
if (currentUser != null) AuthState.SignedIn(currentUser) else AuthState.SignedOut
} else {
AuthState.SignedOut
}
}
}
fun isSignedIn(): Boolean = firebaseAuth.currentUser != null
suspend fun getCurrentUser(): User? {
val currentUser = firebaseAuth.currentUser ?: return null
return try {
val userDoc = firestore.collection("users")
.document(currentUser.uid)
.get()
.await()
userDoc.toObject(User::class.java)
} catch (e: Exception) {
if (e is CancellationException) throw e
logError("Error fetching current user: ${e.message}")
null
}
}
fun onGoogleLoginClicked() {
viewModelScope.launch {
val state = signIn()
_authState.value = state
}
}
private suspend fun signIn(): AuthState {
return try {
if (isSignedIn()) {
val currentUser = getCurrentUser()
Log.d(tag, "이미 로그인 상태입니다. 사용자: $currentUser")
return AuthState.SignedIn(currentUser)
}
val result = buildCredentialRequest()
Log.d(tag, "Credential 요청 성공: $result")
handleSignIn(result)
} catch (e: Exception) {
Log.e(tag, "Sign-in failed: ${e.message}", e)
AuthState.Error("Sign-in failed: ${e.message}")
}
}
private suspend fun buildCredentialRequest(): GetCredentialResponse {
val nonce = generateNonce()
val signInWithGoogleOption = GetSignInWithGoogleOption.Builder(clientId)
.setNonce(nonce)
.build()
val request = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
return credentialManager.getCredential(context = applicationContext, request = request)
}
private fun generateNonce(): String {
val randomBytes = ByteArray(32)
SecureRandom().nextBytes(randomBytes)
return randomBytes.joinToString("") { "%02x".format(it) }
}
private fun logError(message: String) {
Log.e(tag, message)
}
private suspend fun handleSignIn(result: GetCredentialResponse): AuthState {
Log.d(tag, "{handleSignIn 진입 완료}")
val credential = result.credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
) {
try {
val tokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
val authCredential = GoogleAuthProvider.getCredential(
tokenCredential.idToken, null
)
val authResult = firebaseAuth.signInWithCredential(authCredential).await()
val firebaseUser = authResult.user
return if (firebaseUser != null) {
val userExists = checkUserExistsInFirestore(firebaseUser.uid)
if (!userExists) {
val initialProfile = createInitialProfile(firebaseUser)
_pendingUserMap.value = initialProfile
AuthState.SignUpRequired
} else {
val userDocument = firestore.collection("users")
.document(firebaseUser.uid).get().await()
val user = userDocument.toObject(User::class.java)
AuthState.SignedIn(user)
}
} else {
AuthState.SignedOut
}
} catch (e: Exception) {
Log.e(tag, "Sign-in error: ${e.message}", e)
return AuthState.Error("Sign-in failed: ${e.message}")
}
} else {
Log.e(tag, "credential is not GoogleIdTokenCredential")
return AuthState.SignedOut
}
}
private suspend fun checkUserExistsInFirestore(userId: String): Boolean {
return try {
firestore.collection("users").document(userId).get().await().exists()
} catch (e: Exception) {
e.printStackTrace()
false
}
}
private fun createInitialProfile(firebaseUser: FirebaseUser): Map {
return mapOf(
"uid" to firebaseUser.uid,
"email" to firebaseUser.email,
"displayName" to firebaseUser.displayName
)
}
}
Во втором коде, когда я вызываю credentialManager.getCredential(context = context, request = request), он выдает ошибку. Однако я убедился, что настройки GCP и конфигурации SHA-1 верны, поскольку первый код отлично работает в том же проекте.
Что может быть причиной credentialManager.getCredential вызывает сбой во втором коде?
в logcat отображается ошибка «[28444] Консоль разработчика настроена неправильно».
Любой идеи или предложения о том, как устранить эту неполадку буду очень признателен!
У меня возникла проблема с реализацией входа в Google в приложении Android. У меня есть два фрагмента кода: первый работает правильно, а второй дает сбой во время вызова credentialManager.getCredential(context = context, request = request). Первый код (рабочий) [code]class GoogleAuthClient( private val context: Context, private val credentialManager: CredentialManager, private val onSignInComplete: () -> Unit, // 로그인 완료 콜백 추가 private val onSignOutComplete: () -> Unit // 로그아웃 완료 콜백 추가 ) { companion object { private const val TAG = "GoogleAuthClient" }
fun handleSignIn(result: GetCredentialResponse) { val credential = result.credential
when { credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL -> { try { val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
val idToken = googleIdTokenCredential.idToken val email = googleIdTokenCredential.id val displayName = googleIdTokenCredential.displayName
@Composable private fun GoogleLoginButton( onGoogleLoginClick: () -> Unit ) { Row( modifier = Modifier .width(345.dp) .height(54.dp) .background(color = GrayScaleWhite, shape = RoundedCornerShape(size = 10.dp)) .padding(start = 17.dp) .clickable(onClick = onGoogleLoginClick), horizontalArrangement = Arrangement.spacedBy(79.dp, Alignment.Start), verticalAlignment = Alignment.CenterVertically, ) { Image( modifier = Modifier .width(24.dp) .height(24.dp) .background(color = GrayScaleWhite), painter = painterResource(id = R.drawable.google_icon), contentDescription = "google_icon", contentScale = ContentScale.None, ) Text( text = stringResource(id = R.string.google_login), style = TextStyle( fontSize = 18.sp, fontFamily = FontFamily(Font(R.font.inter_18pt_regular)), fontWeight = FontWeight(400), color = Color(0xFF000000), ), ) } } --------------------------- @HiltViewModel class LoginViewModel @Inject constructor( private val credentialManager: CredentialManager, private val clientId: String, @ApplicationContext private val applicationContext: Context // 애플리케이션 컨텍스트 추가 ) : ViewModel() { private val tag = "LoginViewModel: " private val firebaseAuth: FirebaseAuth = Firebase.auth private val firestore: FirebaseFirestore = Firebase.firestore
private val _authState = MutableStateFlow(AuthState.SignedOut) val authState: StateFlow = _authState.asStateFlow()
private val _pendingUserMap = MutableStateFlow(null) val pendingUserMap: StateFlow = _pendingUserMap.asStateFlow()
init { checkInitialAuthState() }
private fun checkInitialAuthState() { viewModelScope.launch { _authState.value = if (isSignedIn()) { val currentUser = getCurrentUser() if (currentUser != null) AuthState.SignedIn(currentUser) else AuthState.SignedOut } else { AuthState.SignedOut } } }
fun isSignedIn(): Boolean = firebaseAuth.currentUser != null
suspend fun getCurrentUser(): User? { val currentUser = firebaseAuth.currentUser ?: return null
return try { val userDoc = firestore.collection("users") .document(currentUser.uid) .get() .await()
userDoc.toObject(User::class.java) } catch (e: Exception) { if (e is CancellationException) throw e logError("Error fetching current user: ${e.message}") null } }
fun onGoogleLoginClicked() { viewModelScope.launch { val state = signIn() _authState.value = state } }
private suspend fun signIn(): AuthState { return try { if (isSignedIn()) { val currentUser = getCurrentUser() Log.d(tag, "이미 로그인 상태입니다. 사용자: $currentUser") return AuthState.SignedIn(currentUser) }
val result = buildCredentialRequest() Log.d(tag, "Credential 요청 성공: $result")
private fun createInitialProfile(firebaseUser: FirebaseUser): Map { return mapOf( "uid" to firebaseUser.uid, "email" to firebaseUser.email, "displayName" to firebaseUser.displayName ) } }
[/code] Во втором коде, когда я вызываю credentialManager.getCredential(context = context, request = request), он выдает ошибку. [b]Однако я убедился, что настройки GCP и конфигурации SHA-1 верны, поскольку первый код отлично работает в том же проекте.[/b] Что может быть причиной credentialManager.getCredential вызывает сбой во втором коде? в logcat отображается ошибка «[28444] Консоль разработчика настроена неправильно». Любой идеи или предложения о том, как устранить эту неполадку буду очень признателен!