DEV Community

Cover image for Firebase User Authentication:Email and Google Sign in on Android with Kotlin
Loveth Nwokike
Loveth Nwokike

Posted on

Firebase User Authentication:Email and Google Sign in on Android with Kotlin

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
  • 🎁 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

di is the short for dependency injection and will be used through out the post.

GitHub logo kulloveth / FirebaseAndroidAuthSample

A sample simplifying firebase authentication with email and gmail in android with Kotlin

FirebaseAndroidAuthSample

A sample simplifying firebase authentication with email and google in android

Libraries used

  • Firebase
  • Hilt-Android
  • Navigation Components
  • ViewModel
  • LiveData
  • ViewBinding
  • Timber

MIT License

Copyright (c) 2021  Loveth Nwokike
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
…

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

Alt Text

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

  • Then you click on connect and finally you get this
    Alt Text

  • confirming that your app has been connected.

  • back in android studio, In the firebase assistant click on

    1. Authentication > email and password authentication >Add firebase authentication to your app
    2. Firestore > Read and write documents with Cloud Firestore > Add Cloud Firestore to your app

    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
    1. Going to firebase, Click on console
    2. Select your app
    3. by the left hand menu select authentication
    4. click on sign in method and enable email/password

Alt Text

  • 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()

Enter fullscreen mode Exit fullscreen mode
  • 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))
}
Enter fullscreen mode Exit fullscreen mode

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)

}
Enter fullscreen mode Exit fullscreen mode

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()))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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("...")
                            }
                        } })
        }
Enter fullscreen mode Exit fullscreen mode

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 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 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" />
Enter fullscreen mode Exit fullscreen mode

Next we configure googlesigninoptions(gso) 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)
Enter fullscreen mode Exit fullscreen mode

FirebaseSource.kt

fun signInWithGoogle(acct: GoogleSignInAccount) = firebaseAuth.signInWithCredential(GoogleAuthProvider.getCredential(acct.idToken,null))

fun fetchUser()=firestore.collection("users").get()
Enter fullscreen mode Exit fullscreen mode

Repository.kt

 fun signInWithGoogle(acct: GoogleSignInAccount) = fireBaseSource.signInWithGoogle(acct)

fun fetchUser() = fireBaseSource.fetchUser()
Enter fullscreen mode Exit fullscreen mode

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
    }
Enter fullscreen mode Exit fullscreen mode

In LoginViewModel we have signin method for both email and gmail

  • For sign in with email

    1. We check if email exist
    2. check if email has been verified
    3. 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
  • For sign in with google

    1. We get the selected google account
    2. then get the display name and email associated with it and post via LiveData

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)

    }

}
Enter fullscreen mode Exit fullscreen mode

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
    }
Enter fullscreen mode Exit fullscreen mode

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()
        }
Enter fullscreen mode Exit fullscreen mode

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 logo kulloveth / FirebaseAndroidAuthSample

A sample simplifying firebase authentication with email and gmail in android with Kotlin

FirebaseAndroidAuthSample

A sample simplifying firebase authentication with email and google in android




















Libraries used

  • Firebase
  • Hilt-Android
  • Navigation Components
  • ViewModel
  • LiveData
  • ViewBinding
  • Timber

MIT License

Copyright (c) 2021  Loveth Nwokike
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
…

Top comments (1)

Collapse
 
nahashonthiongo profile image
NahashonThiongo

hello nija how are you