I've implemented email and password authentication using ViewModel and LiveData. My code looks like this:
AuhtService.kt
fun createNewUser(email: String, password: String): MutableLiveData<AuthData> {
val authenticatedUserLiveData = MutableLiveData<AuthData>()
firebaseAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task: Task<AuthResult> ->
if (task.isSuccessful) {
Log.d("AuthService", "Created new user! $email")
authenticatedUserLiveData.value = AuthData("success", task.result?.user, null)
task.result?.user?.let {
DatabaseService.getDbInstance().collection("users")
.document(it.uid).set(it).addOnCompleteListener { task: Task<Void> ->
Log.d("AuthService", "Added new user to database.")
}
}
} else {
Log.d(
"AuthService",
"Failed to create new user! $email ${task.exception?.message}"
)
authenticatedUserLiveData.value =
AuthData("failure", null, task.exception?.message)
}
}
return authenticatedUserLiveData
}
This class performs the firebase operations and returns livedata of type AuthData which is basically a wrapper around FirebaseUser and a couple of strings to carry error messages in case Authentication fails.
UserAuthViewModel.kt
class UserAuthViewModel : ViewModel() {
private val authService = AuthService()
enum class AuthState {
AUTHENTICATED,
UNAUTHENTICATED,
INVALID_AUTH
}
val authState = MutableLiveData<AuthState>()
val authErrorMsg = MutableLiveData<String>()
var currentUser = MutableLiveData<FirebaseUser>()
init {
authErrorMsg.value = ""
authState.value = AuthState.UNAUTHENTICATED
currentUser = authService.getCurrentUser()
}
fun signInWithEmail(email: String, password: String) {
val result = authService.loginWithEmail(email, password).value
if (result?.status.equals("success")) {
authState.value = AuthState.AUTHENTICATED
} else {
authState.value = AuthState.INVALID_AUTH
authErrorMsg.value = result?.msg
}
currentUser.value = result?.data
}
fun createUserWithEmailAndPassword(email: String, password: String) {
val result = authService.createNewUser(email, password).value
if (result?.status.equals("success")) {
authState.value = AuthState.AUTHENTICATED
} else {
authState.value = AuthState.INVALID_AUTH
Log.d("AuthVM", result?.msg)
authErrorMsg.value = result?.msg
}
currentUser.value = result?.data
}
}
finally my RegisterFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
userAuthViewModel.authState.observe(viewLifecycleOwner, Observer { authState ->
if (authState == UserAuthViewModel.AuthState.AUTHENTICATED) {
Toast.makeText(
context,
"User is ${userAuthViewModel.currentUser.value?.email}",
Toast.LENGTH_SHORT
).show()
}
if(authState == UserAuthViewModel.AuthState.INVALID_AUTH) {
Snackbar.make(view, "${userAuthViewModel.authErrorMsg.value}", Snackbar.LENGTH_SHORT).show()
}
})
register_button.setOnClickListener {
val email = registerEmail_editText.editText?.text
val pwd = registerPassword_editText.editText?.text
userAuthViewModel.createUserWithEmailAndPassword(email.toString(), pwd.toString())
}
goToLoginButton.setOnClickListener{
findNavController().navigate(R.id.action_registerFragment_to_loginFragment)
}
}
The above code is mainly inspired from google docs and MVVM arch. but it behaves very weirdly, first of all I get a snackbar at the start with no text, secondly the error messages are not displayed in the sncakbar when auth fails. What am I doing wrong.
Update: On further testing it seems that result?.msg in my UserAuthViewModel is null, but I'm passing error messages in my AuthService, quite weird.
Update 2: Its seems in either case the else of if (result?.status.equals("success")) is being executed, am I doing string comparison wrong?
Update 3: I figured it out but I'm not sure what is the proper way to solve this. The problem is that although AuthServie returns a MutableLiveData that contains null values initially which will be replaced by proper values later (when async calls from AuthService finish). In the ViewModel I do this to propagate new AuthData when user perfroms a sign up
val result = authService.createNewUser(email, password).value
Problem is that doing this passes the initial LiveData object in the AuthService with null values to the ViewModel, so the View updates with null values, I need a way to so that the View can observe actual values when they get updated from authservice's callbacks.
authState.value = AuthState.AUTHENTICATED. - Rahul sharma