Firebase User Authentication:Email and Google Sign in on Android with Kotlin
Tags: kotlin, android, mobile, firebase
Published: 2021-01-11 22:10:08

Firebase authentication is a fast and easy way of providing secure user verification process. There are several platforms made available with firebase to simplify the process including Facebook, phone auth, GitHub, google, email, twitter and more. We will be discussing google and email implementation in this post.

### By the end of this post you will learn how to :
* Create and setup a firebase account
* Add your app to firebase 
* Enable google and email authentication with firebase
* Implement google authentication
* Implement Email Authentication
* Validate user details and check for errors
* Verify users and recover passwords
* :gift: Save user to cloud firestore
* Fetch signed in user

### What you should already know
* familiarity with writing android apps with Kotlin
* dependency injection(di) with hilt but not very necessary

<mark> di is the short for dependency injection and will be used through out the post. </mark>

{% github kulloveth/FirebaseAndroidAuthSample %}

To create a firebase account you must have a google account which you will be using to sign in. Go to [firebase]
(https://firebase.google.com) and click on sign at the top right corner of the page then proceed to use your preferred google account.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lwycad5gdudvbqghuxxe.png)

Next is to register/add your app to it. There are two ways you can do that either by using the firebase console following these [steps](https://firebase.google.com/docs/android/setup?authuser=0#console) or the easier way using the firebase assistant in android studio

### To add your app to firebase using the firebase assistant in android studio:
* Go to tools > firebase > authentication > email and password    >connect to firebase
* You will be taken to the browser where you can
either choose an existing app or create a new one. If everything is successful you get a popup like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sgrilosz790gh7a106kd.png)

* Then you click on connect and finally you get this
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gpd42x0iywbwo54fere7.png)

* confirming that your app has been connected.

* back in android studio, In the firebase assistant click on         
<ol type="i">
   <li> Authentication > email and password authentication >Add firebase authentication to your app</li>
   <li> Firestore > Read and write documents with Cloud Firestore > Add Cloud Firestore to your app 
</ol>
then allow the app to sync

At this point a `googleservices.json` has been added to your app directory and some dependencies added as well. When you check your app level `build.gradle` you will find out that the following dependency have been added for you
`implementation 'com.google.firebase:firebase-auth:20.0.1'`
`implementation 'com.google.firebase:firebase-firestore:22.0.1'`
and google service plugin added as well
`id 'com.google.gms.google-services'`
also in project level build.gradle google service class path has been added
 `classpath 'com.google.gms:google-services:4.3.4'`

### Authentication by  email 
* Back in firebase console enable email by
<ol type="i">
  <li>Going to [firebase](https://firebase.google.com/?authuser=0), Click on console</li>
  <li>Select your app</li>
  <li>by the left hand menu select authentication</li>
  <li>click on sign in method and enable email/password</li>
</ol>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jaa2p0r40cjilw6gv3d4.png)

* Create a di class to initalize firebaseAuth and firestore

FirebaseModule.kt
```
@Module
@InstallIn(ApplicationComponent::class)
class FireBaseModule {

    @Provides
    @Singleton
    fun provideFireBaseAuth(): FirebaseAuth {
        return FirebaseAuth.getInstance()
    }
 @Provides
    @Singleton
    fun provideFirestore()= FirebaseFirestore.getInstance()

```

* Create a class for firebaseSource

FirebaseSource.kt

```
class FireBaseSource @Inject constructor(private val firebaseAuth: FirebaseAuth,private val firestore: FirebaseFirestore) {

fun signUpUser(email:String,password:String,fullName:String) = firebaseAuth.createUserWithEmailAndPassword(email,password)

fun signInUser(email: String,password: String) = firebaseAuth.signInWithEmailAndPassword(email,password)

fun saveUser(email: String,name:String)=firestore.collection("users").document(email).set(User(email = email,fullName = name))
}
```
here we have methods to sign up, sign and save a user
* `firebaseAuth.createUserWithEmailAndPassword(email,password)`
signs up a user with an email and password
* `firebaseAuth.signInWithEmailAndPassword(email,password)`
signs in user with an existing email and password

* `firestore.collection("users").document(email).set(User(email = email,fullName = name))` creates users document and save a user to it
Repository.kt

```
class Repository @Inject constructor(private val fireBaseSource: FireBaseSource) {

fun signUpUser(email: String, password: String, fullName: String) = fireBaseSource.signUpUser(email, password, fullName)

fun signInUser(email: String, password: String) = fireBaseSource.signInUser(email, password)

fun saveUser(email: String, name: String) = fireBaseSource.saveUser(email, name)

}
```
SignUpViewModel.kt
```
class SignUpViewModel @ViewModelInject constructor(
    private val repository: Repository,
    private val networkControl: NetworkControl,
    private val firebaseAuth: FirebaseAuth
) : ViewModel() {

    private val userLiveData = MutableLiveData<Resource<User>>()
    fun signUpUser(email: String, password: String, fullName: String): LiveData<Resource<User>> {
                when {
                    TextUtils.isEmpty(email) && TextUtils.isEmpty(password) && TextUtils.isEmpty( fullName ) -> {
                        userLiveData.postValue(Resource.error(null, "field must not be empty"))
                    }
                    password.length < 8 -> {
                        userLiveData.postValue( Resource.error(null, "password must not be less than 8"))
                    }
                    networkControl.isConnected() -> {
                            userLiveData.postValue(Resource.loading(null))
                        firebaseAuth.fetchSignInMethodsForEmail(email).addOnCompleteListener {
                            if (it.result?.signInMethods?.size == 0) {
                                repository.signUpUser(email, password, fullName).addOnCompleteListener { task ->
                                        if (task.isSuccessful) {
                                            firebaseAuth.currentUser?.sendEmailVerification()
                                            userLiveData.postValue( Resource.success( User( email = email, fullName = fullName )))
                                        } else {
                                            userLiveData.postValue( Resource.error(null, it.exception?.message.toString()))
                                        } }
                            } else {
                                userLiveData.postValue(Resource.error(null, "email already exist"))
                            } }
                    } else -> {
                    userLiveData.postValue(Resource.error(null, "No internet connection"))
            } }
        return userLiveData
   }

  fun saveUser(email: String, name: String) {
        repository.saveUser(email, name).addOnCompleteListener {
            if (it.isSuccessful) {
            _saveUserLiveData.postValue(Resource.success(User(email,name)))
            }else{
                _saveUserLiveData.postValue(Resource.error(null,it.exception?.message.toString()))
            }
        }
    }
}
```
#### In the SignupViewModel class we check for errors and validate user details
* checks if the fields are empty
* checks if the password field is less than 8
* checks if the email already exists 

then throws up suitable errors for all cases otherwise the user is successfully signed up with their details and email verification is sent to them `firebaseAuth.currentUser?.sendEmailVerification()`
* a method to save to the user detail to firestore if successfully signed up

SignUpFragment.kt
```
binding?.signUpBtn?.setOnClickListener {
            val emailText = binding?.emailEt?.text?.toString()
            val passwordText =  binding?.passwordEt?.text.toString()
            val fullNameText =  binding?.fullNameEt?.text?.toString()
            viewModel.signUpUser( emailText.toString(), passwordText, fullNameText.toString()).observe(viewLifecycleOwner, {
                        when (it.status) {
                            Status.SUCCESS -> {
viewModel.saveUser( it.data?.email.toString(), it.data?.fullName.toString())
                                view.showsnackBar("User account registered")
                            }
                            Status.ERROR -> {
                                view.showsnackBar(it.message!!)
                            }
                            Status.LOADING -> {
                                view.showsnackBar("...")
                            }
                        } })
        }
```
In the signup fragment we observe the signup user method, save user if successfully signed up and display message with the snackBar for changes that occur with the success, error and loading cases 

### Gmail sign in 
To signup up user with gmail
* Enable gmail signin from firebase console
* Add required dependency ` implementation 'com.google.android.gms:play-services-auth:19.0.0'`
* add your certificate [fingerprint](https://developers.google.com/android/guides/client-auth?authuser=0) to your app from project settings in the console
* Ensure the correct package name and fingerprint is registered with the OAuth 2.0 Client IDs part of [credentials](https://console.cloud.google.com/apis/credentials?authuser=0&pli=1&project=wellnesscity-efcc6&folder=&organizationId=) page of the GCP platform

In your layout add google button 
```
  <com.google.android.gms.common.SignInButton
            android:id="@+id/google_signIn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dimens_40dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/orTv"
            android:layout_marginBottom="@dimen/dimens_100dp"
            android:layout_marginStart="@dimen/standard_padding"
            android:layout_marginEnd="@dimen/standard_padding" />
```
Next we configure googlesigninoptions<mark>(gso)</mark> in the di class calling the requestidToken and requestEmail then create the googleClient object with gso configured

FirebaseModule.kt
```
   @Provides
    @Singleton
    fun provideGso(@ApplicationContext context: Context) = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(context.getString(R.string.default_web_client_id))
        .requestEmail()
        .build()

    @Provides
    @Singleton
    fun provideGoogleClient(@ApplicationContext context: Context, gso:GoogleSignInOptions)= GoogleSignIn.getClient(context, gso)
```
FirebaseSource.kt
```
fun signInWithGoogle(acct: GoogleSignInAccount) = firebaseAuth.signInWithCredential(GoogleAuthProvider.getCredential(acct.idToken,null))

fun fetchUser()=firestore.collection("users").get()
```
Repository.kt
```
 fun signInWithGoogle(acct: GoogleSignInAccount) = fireBaseSource.signInWithGoogle(acct)

fun fetchUser() = fireBaseSource.fetchUser()
```
### SignIn User with email or gmail

LoginViewModel.kt
```
fun signInUser(email: String, password: String): LiveData<Resource<User>> {
        when {
            TextUtils.isEmpty(email) && TextUtils.isEmpty(password) -> {
                userLiveData.postValue(Resource.error(null, "Enter email and password"))
            }
            networkControl.isConnected() -> {
                userLiveData.postValue(Resource.loading(null))
                firebaseAuth.fetchSignInMethodsForEmail(email).addOnCompleteListener {
                    if (it.result?.signInMethods?.size == 0) {
                        userLiveData.postValue(Resource.error(null, "Email does not exist"))
                    } else {
                        repository.signInUser(email, password).addOnCompleteListener { task ->
                            if (task.isSuccessful) {
                                firebaseAuth.currentUser?.isEmailVerified?.let { verified ->
                                    if (verified) {
                                        repository.fetchUser().addOnCompleteListener { userTask ->
                                            if (userTask.isSuccessful) {
                                                userTask.result?.documents?.forEach {
                                                    if (it.data!!["email"] == email) {
                                                        val name = it.data?.getValue("fullName")
                                                        userLiveData.postValue(Resource.success(User(firebaseAuth.currentUser?.email!!, name?.toString()!!)
                                                            )) } }
                                            } else {
                                                userLiveData.postValue(Resource.error(null, userTask.exception?.message.toString()))
                                            }
                                        }
                                    } else {
                                        userLiveData.postValue(Resource.error(null, "Email is not verified, check your email"))
                                    } }
                            } else {
                                userLiveData.postValue(Resource.error(null, task.exception?.message.toString()))
                                Timber.e(task.exception.toString())
                            } } } }
            }
            else -> {
                userLiveData.postValue(Resource.error(null, "No internet connection"))
            }
        }
        return userLiveData
    }

 fun signInWithGoogle(acct: GoogleSignInAccount): LiveData<Resource<User>> {
        repository.signInWithGoogle(acct).addOnCompleteListener { task ->
            if (task.isSuccessful) {
                gMailUserLiveData.postValue(
                    Resource.success(
                        User(
                            firebaseAuth.currentUser?.email!!,
                            firebaseAuth.currentUser?.displayName!!
                        )
                    )
                )
            } else {
                gMailUserLiveData.postValue(Resource.error(null, "couldn't sign in user"))
            }

        }
        return gMailUserLiveData
    }
```
In LoginViewModel we have signin method for both email and gmail
* For sign in with email
<ol type="i">
<li>We check if email exist</li>
<li>check if email has been verified</li>
<li>then check the email in firestore to get the full name associated with it during registration and post it through LiveData for the view to observe on sign in</li>
</ol>

* For sign in with google
<ol type="i">
<li>We get the selected google account</li>
<li>then get the display name and email associated with it and post via LiveData</li>
</ol>

LoginFragment.kt
```
@AndroidEntryPoint
class LoginFragment : Fragment() {

    private val viewModel: LoginViewModel by viewModels()
    private var binding: FragmentLoginBinding? = null

    @Inject
    lateinit var googleSignInClient: GoogleSignInClient

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        binding = FragmentLoginBinding.inflate(layoutInflater)
        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)



        binding?.signUpTv?.setOnClickListener {

            if (findNavController().currentDestination?.id == R.id.loginFragment) {
                NavHostFragment.findNavController(this)
                    .navigate(LoginFragmentDirections.actionLoginFragmentToSignUpFragment())
            }
        }
        binding?.signInBtn?.setOnClickListener {
            val emailText = binding?.emailEt?.text?.toString()
            val passwordText = binding?.passwordEt?.text.toString()
            viewModel.signInUser(emailText!!, passwordText).observe(viewLifecycleOwner, {
                when (it.status) {
                    Status.LOADING -> {
                        view.showsnackBar("...")
                    }

                    Status.SUCCESS -> {
                        view.showsnackBar("Login successful")
                        if (findNavController().currentDestination?.id == R.id.loginFragment) {
                            NavHostFragment.findNavController(this)
                                .navigate(
                                    LoginFragmentDirections.actionLoginFragmentToDashBoardFragment(
                                        it.data?.fullName!!
                                    )
                                )
                        }
                    }

                    Status.ERROR -> {
                        view.showsnackBar(it.message!!)
                    }
                }
            })
        }

        binding?.googleSignIn?.setOnClickListener {
            signIn()
        }
    }


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

            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                val account = task.getResult(ApiException::class.java)

                viewModel.signInWithGoogle(account!!).observe(viewLifecycleOwner, {
                    if (it.status == Status.SUCCESS) {
                        if (findNavController().currentDestination?.id == R.id.loginFragment) {
                            NavHostFragment.findNavController(this).navigate(
                                LoginFragmentDirections.actionLoginFragmentToDashBoardFragment(it?.data?.fullName!!)
                            )
                            // Timber.d("display ${fAuth.currentUser?.displayName} ")
                        }
                    } else if (it.status == Status.ERROR) {
                        requireView().showsnackBar(it.message!!)
                    }
                })
            } catch (e: ApiException) {
                Toast.makeText(requireContext(), e.message, Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun signIn() {

        val signInIntent: Intent = googleSignInClient.signInIntent

        startActivityForResult(signInIntent, RC_SIGN_IN)

    }

}
```
In the LoginFragment 
* We observe the sign in method and get the user details
* We receive the data from the google account in the onActivityResult
* call startActivityForResult for the request code received
* If successful we navigate the user to dashboard activity and display their name

### Forgot password
* In the FirebaseSource.kt we create the reset password method `fun sendForgotPassword(email: String) = firebaseAuth.sendPasswordResetEmail(email)` 
* In the Repository.kt we call the method `fun sendForgotPassword(email: String)=fireBaseSource.sendForgotPassword(email)`
* In the LoginViewModel we create a LiveData to observe for when the email is sent
```
  fun sendResetPassword(email: String): LiveData<Resource<User>> {

        when {
            TextUtils.isEmpty(email) -> {
                sendResetPasswordLiveData.postValue(Resource.error(null, "Enter registered email"))
            }
            networkControl.isConnected() -> {
                repository.sendForgotPassword(email).addOnCompleteListener { task ->
                    sendResetPasswordLiveData.postValue(Resource.loading(null))
                    if (task.isSuccessful) {
                        sendResetPasswordLiveData.postValue(Resource.success(User()))
                    } else {
                        sendResetPasswordLiveData.postValue(
                            Resource.error(
                                null,
                                task.exception?.message.toString()
                            )
                        )
                    }
                }
            }
            else -> {
                sendResetPasswordLiveData.postValue(Resource.error(null, "No internet connection"))
            }
        }
        return sendResetPasswordLiveData
    }
``` 
LoginFragment.kt
```
 val dialog = AlertDialog.Builder(requireContext())
        val inflater = (requireActivity()).layoutInflater
        val v = inflater.inflate(R.layout.forgot_password, null)
        dialog.setView(v)
            .setCancelable(false)
        val d = dialog.create()
        val emailEt = v.findViewById<TextInputEditText>(R.id.emailEt)
        val sendBtn = v.findViewById<MaterialButton>(R.id.sendEmailBtn)
        val dismissBtn = v.findViewById<MaterialButton>(R.id.dismissBtn)


        sendBtn.setOnClickListener {
            viewModel.sendResetPassword(emailEt.text.toString()).observeForever {
                if (it.status == Status.SUCCESS){
                    view.showsnackBar("reset email sent")
                }else{
                    view.showsnackBar(it.message.toString())
                }
            }
        }
        dismissBtn.setOnClickListener {
            d.dismiss()
        }


        binding?.forgotPasswordTv?.setOnClickListener {
            d.show()
        }
```
 We create a dialog that is displayed when forgot password is clicked on so the user can enter a registered email and received a link to reset their password

Finally we have a working user authentication to verify and validate user with email/password and google. You can checkout this repository for a complete implementation.
{% github kulloveth/FirebaseAndroidAuthSample %}