Firebase Authentication (Google Sign-in) With Android Jetpack Compose

November 2, 2021

Prerequisite

Add Firebase to Android

Depedencies

dependencies {
    // Import the BoM for the Firebase platform
    implementation platform('com.google.firebase:firebase-bom:29.0.0')

    // Declare the dependency for the Firebase Authentication library
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-auth-ktx'

    // Also declare the dependency for the Google Play services library and specify its version
    implementation 'com.google.android.gms:play-services-auth:19.2.0'
}

Also, remember setup your app’s SHA-1 fingerprint at Firebase Settings.

Enable Google Sign-In at Auth section of Firebase Console.

Code

Activity

class MainActivity : ComponentActivity() {
    private val auth: FirebaseAuth = Firebase.auth
    private lateinit var googleSignInClient: GoogleSignInClient
    private lateinit var authResultLauncher: ActivityResultLauncher<Intent>
    private val viewModel: MainViewModel by viewModels()


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

        auth.addAuthStateListener { auth ->
            Timber.d("addAuthStateListener: ${auth.currentUser}")
            viewModel.setCurrentUser(auth.currentUser)
        }

        // R.string.default_web_client_id is created automatically as per google-services.json,
        // though sometimes the IDE might not recognize it
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build()

        googleSignInClient = GoogleSignIn.getClient(this, gso)

        authResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            // if (result.resultCode == Activity.RESULT_OK) {
            // There are no request codes
            val data: Intent? = result.data
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                // Google Sign In was successful, authenticate with Firebase
                val account = task.getResult(ApiException::class.java)!!
                Timber.d("firebaseAuthWithGoogle:" + account.id)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                // Google Sign In failed, update UI appropriately
                Timber.w(e, "Google sign in failed")
            }
            // }
        }

        setContent {
            JourneyTheme {
                UserProfileScreen(
                    viewModel = viewModel,
                    onSignIn = { signIn() },
                    onSignOut = { auth.signOut() }
                )
            }
        }        

    }

    fun signIn() {
        val signInIntent = googleSignInClient.signInIntent
        // startActivityForResult(signInIntent, RC_SIGN_IN)

        authResultLauncher.launch(signInIntent)
    }

    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        auth.signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information
                    Timber.d("signInWithCredential:success")
                    // val user = auth.currentUser
                    // updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    Timber.w(task.exception, "signInWithCredential:failure")
                    // updateUI(null)
                }
            }
    }    
}

Composable

@Composable
fun UserProfileScreen(viewModel: MainViewModel, onSignIn: () -> Unit, onSignOut: () -> Unit) {
    val currentUser = viewModel.currentUser

    Column(modifier = Modifier.padding(16.dp)) {
        if (currentUser == null) {
            Button(onClick = { onSignIn() }){
                Text(text = "Sign in with Google")
            }
        }
        else {
            Text(text = "Welcome, ${currentUser.displayName}")
            Button(onClick = { onSignOut() } ) {
                Text(text = "Sign out")
            }
        }
    }
}

ViewModel

class MainViewModel : ViewModel() {
    var currentUser by mutableStateOf<FirebaseUser?>(null)
        private set

    @JvmName("assignCurrentUser")
    fun setCurrentUser(user: FirebaseUser?) {
        currentUser = user
    }
}

Potential Errors

Requests to this API firebaseinstallations.googleapis.com method google.firebase.installations.v1.FirebaseInstallationsService.CreateInstallation are blocked.

If you enable restrictions for your API, goto Google Cloud Console -> Credentials, Edit API key -> API restrictions and enable Firebase Installations API.

com.google.firebase.FirebaseException: An internal error has occurred. [ Requests to this API identitytoolkit method google.cloud.identitytoolkit.v1.AuthenticationService.SignInWithIdp are blocked. ]

Enable Identity Toolkit API to your API restrictions.

Requests to this API securetoken.googleapis.com method google.identity.securetoken.v1.SecureToken.GrantToken are blocked.

Enable Token Service API to your API restrictions.

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.