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.
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.
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:
confirming that your app has been connected.
-
back in android studio, In the firebase assistant click on
- Authentication > email and password authentication >Add firebase authentication to your app
- 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
- Going to firebase, Click on console
- Select your app
- by the left hand menu select authentication
- click on sign in method and enable email/password
- 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 passwordfirestore.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 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" />
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)
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
- We check if email exist
- check if email has been verified
- 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
- We get the selected google account
- 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)
}
}
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.
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)
hello nija how are you