Problem mit der Google-Anmeldeimplementierung in ViewModelAndroid

Forum für diejenigen, die für Android programmieren
Guest
 Problem mit der Google-Anmeldeimplementierung in ViewModel

Post by Guest »

Ich habe ein Problem mit der Implementierung meiner Google-Anmeldung in einer Android-Anwendung. Ich habe zwei Codeteile: Der erste funktioniert ordnungsgemäß, während der zweite beim Aufruf von credentialManager.getCredential(context = context, request = request).
fehlschlägt Erster Code (funktioniert)

Code: Select all

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

Log.d(TAG, "Login successful: $email")
Toast.makeText(context, "Welcome, $displayName!", Toast.LENGTH_SHORT).show()
onSignInComplete()  // 로그인 완료 알림

} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Invalid Google ID token", e)
Toast.makeText(context, "Login failed: Invalid token", Toast.LENGTH_SHORT)
.show()
}
}

else ->  {
Log.e(TAG, "Unexpected credential type")
Toast.makeText(context, "Login failed: Unexpected error", Toast.LENGTH_SHORT).show()
}
}
}

fun signOut() {
CoroutineScope(Dispatchers.Main).launch {
try {
credentialManager.clearCredentialState(
ClearCredentialStateRequest(
ClearCredentialStateRequest.TYPE_CLEAR_CREDENTIAL_STATE
)
)
Toast.makeText(context, "Signed out successfully", Toast.LENGTH_SHORT).show()
onSignOutComplete()  // 로그아웃 완료 알림
} catch (e: Exception) {
Log.e(TAG, "Logout failed", e)
Toast.makeText(context, "Logout failed", Toast.LENGTH_SHORT).show()
}
}
}
}
class MainActivity : ComponentActivity() {
private lateinit var googleAuthClient: GoogleAuthClient
private lateinit var credentialManager: CredentialManager
private var isSignedIn = false
private lateinit var signInOutText: TextView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

signInOutText = TextView(this).apply {
text = "Sign In"
textSize = 18f
setPadding(32, 32, 32, 32)
setOnClickListener { handleSignInOutClick() }
}
setContentView(signInOutText)

credentialManager = CredentialManager.create(this)
googleAuthClient = GoogleAuthClient(
this,
credentialManager,
onSignInComplete = {
isSignedIn = true
signInOutText.text = "Sign Out"
},
onSignOutComplete = {
isSignedIn = false
signInOutText.text = "Sign In"
}
)
}

private fun handleSignInOutClick() {
if (!isSignedIn) {
signInWithGoogle()
} else {
googleAuthClient.signOut()
}
}

private fun signInWithGoogle() {
val nonce = generateNonce()
val signInWithGoogleOption = GetSignInWithGoogleOption.Builder(
"909240456210-gomip46dc5200lbd8vdrs2ii94clss4t.apps.googleusercontent.com")
.setNonce(nonce)
.build()
Log.d("haha", "signInWithGoogleOption 성공")

val request = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()

Log.i("haha", "GetCredentialRequest 성공")

CoroutineScope(Dispatchers.Main).launch {
try {
val result = credentialManager.getCredential(
request = request,
context = this@MainActivity
)
Log.d("haha", "getCredential 성공")

googleAuthClient.handleSignIn(result)
} catch (e: Exception) {
println("Login failed: ${e.message}")
}
}
}

private fun generateNonce(): String {
val randomBytes = ByteArray(32)
SecureRandom().nextBytes(randomBytes)
return randomBytes.joinToString("") { "%02x".format(it) }
}
}
Zweiter Code (funktioniert nicht)

Code: Select all

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
)
}
}

Wenn ich im zweiten Code credentialManager.getCredential(context = context, request = request) aufrufe, wird ein Fehler ausgegeben. Ich habe jedoch überprüft, ob die GCP-Einstellungen und SHA-1-Konfigurationen korrekt sind, da der erste Code im selben Projekt einwandfrei funktioniert.
Was könnte die Ursache sein? credentialManager.getCredential-Aufruf schlägt im zweiten Code fehl?
Der in Logcat angezeigte Fehler lautet „[28444] Die Entwicklerkonsole ist nicht korrekt eingerichtet.“
Beliebig Erkenntnisse oder Vorschläge zur Behebung dieses Problems Problem wäre sehr dankbar!

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post