Skip to content

Commit

Permalink
Use CredentialManager for Google Sign-In (#311)
Browse files Browse the repository at this point in the history
* migrate Android app from GoogleSignIn to CredentialManager

* retrieve CredentialManager lazily
  • Loading branch information
cbeyls authored Apr 30, 2024
1 parent 8964491 commit 53fe3a9
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 80 deletions.
5 changes: 4 additions & 1 deletion androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ dependencies {
implementation(libs.firebase.crashlytics.ktx)
implementation(libs.firebase.messaging)

implementation(libs.play.services.auth)
// Credentials
implementation(libs.androidx.credentials)
implementation(libs.androidx.credentials.playServicesAuth)
implementation(libs.googleid)

implementation(libs.koin.androidx.compose) {
exclude(group = "androidx.appcompat", module = "appcompat")
Expand Down
148 changes: 69 additions & 79 deletions androidApp/src/main/java/fr/paug/androidmakers/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package fr.paug.androidmakers

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
Expand All @@ -15,28 +14,34 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.exceptions.GetCredentialException
import androidx.lifecycle.lifecycleScope
import com.androidmakers.ui.MainLayout
import com.androidmakers.ui.common.SigninCallbacks
import com.androidmakers.ui.common.navigation.UserData
import com.androidmakers.ui.theme.AndroidMakersTheme
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import com.google.firebase.messaging.FirebaseMessaging
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.auth.GoogleAuthProvider
import dev.gitlive.firebase.auth.auth
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import org.koin.compose.KoinContext

class MainActivity : ComponentActivity() {

private val credentialManager: CredentialManager by lazy(LazyThreadSafetyMode.NONE) {
CredentialManager.create(this)
}

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

Expand Down Expand Up @@ -79,8 +84,8 @@ class MainActivity : ComponentActivity() {
versionCode = BuildConfig.VERSION_CODE.toString(),
deeplink = deeplink,
signinCallbacks = SigninCallbacks(
signin = { signin() },
signout = { signout() },
signin = ::signIn,
signout = ::signOut,
)
)
}
Expand All @@ -89,89 +94,74 @@ class MainActivity : ComponentActivity() {
}

private fun logFCMToken() {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("MainActivity", "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
val token = task.result
Log.d("MainActivity", token)
})
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

when (requestCode) {
REQ_SIGNIN -> {
val task: Task<GoogleSignInAccount> =
GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account: GoogleSignInAccount = task.getResult(ApiException::class.java)
val idToken = account.idToken
when {
idToken != null -> {
// Got an ID token from Google. Use it to authenticate
// with Firebase.
val firebaseCredential = GoogleAuthProvider.credential(idToken, null)
val auth = Firebase.auth

CoroutineScope(Dispatchers.Default).launch {
val result = auth.signInWithCredential(firebaseCredential)
// Sign in success, update UI with the signed-in user's information
lifecycleScope.launch {
UserData().apply {
userRepository.setUser(result.user)
val uid = result.user?.uid
if (uid != null) {
mergeBookmarksUseCase(uid)
}
}

println("user id=${result.user?.uid}")
println("idToken=${result.user?.getIdToken(true)}")
}
}
}

else -> {}
}
} catch (e: ApiException) {
e.printStackTrace()
lifecycleScope.launch {
UserData().userRepository.setUser(null)
}
lifecycleScope.launch {
try {
val token = FirebaseMessaging.getInstance().token.await()
Log.d(TAG, "FCM registration token: $token")
} catch (e: Exception) {
if (e is CancellationException) {
throw e
}
Log.w(TAG, "Fetching FCM registration token failed", e)
}
}
}

fun signout() {
val activity = this
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("985196411897-r7edbi9jgo3hfupekcmdrg66inonj0o5.apps.googleusercontent.com")
.build()
val googleSignInClient = GoogleSignIn.getClient(activity, gso)

private fun signOut() {
lifecycleScope.launch {
Firebase.auth.signOut()
googleSignInClient.signOut()
googleSignInClient.revokeAccess()
credentialManager.clearCredentialState(ClearCredentialStateRequest())
UserData().userRepository.setUser(null)
}
}

fun signin() {
val activity = this
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("985196411897-r7edbi9jgo3hfupekcmdrg66inonj0o5.apps.googleusercontent.com")
private fun signIn() {
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(SERVER_CLIENT_ID)
.build()
val googleSignInClient = GoogleSignIn.getClient(activity, gso)
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()

lifecycleScope.launch {
try {
val response = credentialManager.getCredential(this@MainActivity, request)
val credential = response.credential
if (credential is CustomCredential && credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
handleGoogleIdTokenCredential(GoogleIdTokenCredential.createFrom(credential.data))
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e)
}
} else {
Log.e(TAG, "Unexpected type of credential")
}
} catch (e: GetCredentialException) {
Log.e(TAG, "Retrieving of credential failed", e)
UserData().userRepository.setUser(null)
}
}
}

private suspend fun handleGoogleIdTokenCredential(googleIdTokenCredential: GoogleIdTokenCredential) {
// Got an ID token from Google. Use it to authenticate with Firebase.GoogleSignIn
val firebaseCredential = GoogleAuthProvider.credential(googleIdTokenCredential.idToken, null)
val result = Firebase.auth.signInWithCredential(firebaseCredential)
// Sign in success, update UI with the signed-in user's information
with(UserData()) {
userRepository.setUser(result.user)
result.user?.uid?.let {
mergeBookmarksUseCase(it)
}
}

activity.startActivityForResult(googleSignInClient.signInIntent, REQ_SIGNIN)
Log.d(TAG, "user id=${result.user?.uid}")
Log.d(TAG, "idToken=${result.user?.getIdToken(true)}")
}

companion object {
const val REQ_SIGNIN = 33
private const val TAG = "MainActivity"
private const val SERVER_CLIENT_ID = "985196411897-r7edbi9jgo3hfupekcmdrg66inonj0o5.apps.googleusercontent.com"
}
}
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ sdk-compile = "34"
sdk-min = "24"
androidxActivity = "1.9.0"
androidxCore = "1.13.0"
androidxCredentials = "1.2.2"
androidxLifecycle = "2.7.0"
apollo = "4.0.0-beta.6"
compose = "1.6.4"
Expand All @@ -15,6 +16,7 @@ kotlin = "2.0.0-RC1"
coroutines = "1.8.0"
material3 = "1.2.1"
google-services-plugin = "4.4.1"
googleid = "1.1.0"
crashlytics-plugin = "2.9.9"
moko = "0.24.0-alpha-5"
moko-graphics = "0.9.0"
Expand All @@ -32,6 +34,8 @@ okio = "3.9.0"
[libraries]
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "androidxCredentials" }
androidx-credentials-playServicesAuth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "androidxCredentials" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
apollo-adapters = { module = "com.apollographql.apollo3:apollo-adapters", version.ref = "apollo" }
Expand All @@ -48,6 +52,7 @@ espresso-core = "androidx.test.espresso:espresso-core:3.5.1"
firebase-bom = "com.google.firebase:firebase-bom:32.8.1"
firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx" }
firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
junit = "junit:junit:4.13.2"
kotlin-plugin-compose = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
Expand Down

0 comments on commit 53fe3a9

Please sign in to comment.