Monday, June 7, 2021

How to implement sign-in via Google account with Firebase at the Android app

Google and Firebase manuals are not ideal and sometimes even contradicting. So I met some obstacles trying to implement simple sign in process with Google via the Firebase at the Android app. Some of problems are described here.

Finally, nuclear solution worked for me, so I needed to recreate the Firebase project. Here is the consequence of steps that worked:

  1. Delete and recreate values at the local computer storage
    1. Delete SHA-1 key at the local keystore and generate new debug SHA-1 via new build.
    2. Rename the package via refactoring, e. g. from com.example.myproject to com.example.mynewproject. Clear some leftovers remaining after refactoring by searching old name "com.example.myproject" in the project files and manually renaming them. Package name is the same essential as SHA-1 for Google Cloud and Firebase consoles. So these consoles don't allow to use old SHA-1 keys from the old deleted projects. Neither might they allow to use old project names which are unique for the particular app I guess.
  2. Delete and recreate projects in the cloud
    1. Delete cloud projects:
      1. Delete Firebase project and verify in the Firebase console that there no active projects yet.
      2. Delete Google Cloud project. Verify at the consequent console that there are no active projects listed. Often Google Cloud is deleted automatically after deleting the Firebase project.
    2. Recreate cloud projects:
      1. At the Firebase console, create new project.
      2. At the Google Cloud Console, nothing is needed to be done. Despite that Firebase manual cross references to Google manual directing us to create new Google Cloud app, we don't need to do this. Because later it will be created automatically by Firebase.
      3. In the created Firebase project create an Android app, pass there new package name and new SHA-1 key that were created at the previous steps.
      4. In the Google Cloud Console, see that Firebase automatically created Cloud project with the same name as was passed to Firebase. Then confirm that two of OAuth 2.0 Client IDs were automatically created there by Firebase.
    3. Final changes on local:
      1. From the Firebase console, download new google-services.json file and delete old file at the local machine which similarly named in the app folder.
      2. In the Google Cloud Console, copy client id labelled "Web application" (not "Android") and hardcode it in the local app code:

                                val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)

                                .requestIdToken(getString(R.string.default_web_client_id_hardcoded))

For the code itself, this is the only part where we can rely on Google and Firebase official manuals. Here is an example of Google sign in fragment that works with the settings listed above:

package com.exa.mynewproject.login

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.exa.mynewproject.R
import com.exa.mynewproject.list.ListNonRespondedActivity
import com.exa.mynewproject.unsorted.Global.Companion.TAG
import com.exa.mynewproject.unsorted.Global.Companion.toastString
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.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.SignInButton
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase

private const val RC_SIGN_IN = 100

class GoogleSignInFragment : Fragment(){
    lateinit var auth: FirebaseAuth
    var googleSignInClient: GoogleSignInClient? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id_hardcoded))
            .requestEmail()
            .build()
        googleSignInClient = this.activity?.let { GoogleSignIn.getClient(it, gso) }
        auth = Firebase.auth
    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
            val view = inflater.inflate(R.layout.fragment_google_sign_in, container, false)
            val signInButton: SignInButton = view.findViewById<View>(R.id.sign_in_button) as SignInButton
            signInButton.setOnClickListener {
            signIn()
        }
        signInButton.setSize(SignInButton.SIZE_STANDARD)
        return view
    }
    override fun onStart() {
        super.onStart()
        val currentUser = auth.currentUser
        updateUI(currentUser)
    }
    private fun signIn() {
        val signInIntent = googleSignInClient?.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            handleSignInResult(task)
        }
    }
    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        this.activity?.let {
            auth.signInWithCredential(credential)
                .addOnCompleteListener(it) { task ->
                    if (task.isSuccessful) {
                        Log.i(TAG, "signInWithCredential:success")
                        val user = auth.currentUser
                        updateUI(user)
                    } else {
                        Log.i(TAG, "signInWithCredential:failure", task.exception)
                        updateUI(null)
                    }
                }
        }
    }
    private fun updateUI(model: FirebaseUser?){
        val signInStatus = if (model == null) getString(R.string.user_not_signed) else
            getString(R.string.welcome) + model.displayName
            toastString(signInStatus)
        if (model != null) {
            val intent = Intent(this.activity, ListNonRespondedActivity::class.java)
            startActivity(intent)
        }
    }
    private fun handleSignInResult(task: Task<GoogleSignInAccount>) {
        try {
            val account = task.getResult(ApiException::class.java)!!
            Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
            firebaseAuthWithGoogle(account.idToken!!)
        } catch (e: ApiException) {
            Log.w(TAG, "Google sign in failed", e)
            updateUI(null)
        }
    }
}

No comments:

Post a Comment