Skip to content

Commit 502961f

Browse files
committed
refactor(anonymous): move Firebase calls to ViewModel
1 parent 531aae6 commit 502961f

File tree

2 files changed

+174
-119
lines changed

2 files changed

+174
-119
lines changed
Lines changed: 31 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
package com.google.firebase.quickstart.auth.kotlin
22

33
import android.os.Bundle
4-
import android.text.TextUtils
5-
import android.util.Log
64
import android.view.LayoutInflater
75
import android.view.View
86
import android.view.ViewGroup
9-
import android.widget.Toast
10-
import com.google.firebase.auth.EmailAuthProvider
11-
import com.google.firebase.auth.FirebaseAuth
12-
import com.google.firebase.auth.FirebaseUser
13-
import com.google.firebase.auth.ktx.auth
14-
import com.google.firebase.ktx.Firebase
15-
import com.google.firebase.quickstart.auth.R
7+
import androidx.fragment.app.viewModels
8+
import androidx.lifecycle.Lifecycle
9+
import androidx.lifecycle.lifecycleScope
10+
import androidx.lifecycle.repeatOnLifecycle
1611
import com.google.firebase.quickstart.auth.databinding.FragmentAnonymousAuthBinding
12+
import kotlinx.coroutines.launch
1713

1814
class AnonymousAuthFragment : BaseFragment() {
1915
private var _binding: FragmentAnonymousAuthBinding? = null
2016
private val binding: FragmentAnonymousAuthBinding
2117
get() = _binding!!
2218

23-
private lateinit var auth: FirebaseAuth
19+
private val viewModel by viewModels<AnonymousAuthViewModel>()
2420

2521
override fun onCreateView(
2622
inflater: LayoutInflater,
@@ -35,134 +31,50 @@ class AnonymousAuthFragment : BaseFragment() {
3531
super.onViewCreated(view, savedInstanceState)
3632
setProgressBar(binding.progressBar)
3733

38-
// Initialize Firebase Auth
39-
auth = Firebase.auth
40-
41-
// Click listeners
4234
binding.buttonAnonymousSignIn.setOnClickListener {
43-
signInAnonymously()
35+
viewModel.signInAnonymously()
4436
}
37+
4538
binding.buttonAnonymousSignOut.setOnClickListener {
46-
signOut()
47-
}
48-
binding.buttonLinkAccount.setOnClickListener {
49-
linkAccount()
39+
viewModel.signOut()
5040
}
51-
}
52-
53-
override fun onStart() {
54-
super.onStart()
55-
// Check if user is signed in (non-null) and update UI accordingly.
56-
val currentUser = auth.currentUser
57-
updateUI(currentUser)
58-
}
5941

60-
private fun signInAnonymously() {
61-
showProgressBar()
62-
auth.signInAnonymously()
63-
.addOnCompleteListener(requireActivity()) { task ->
64-
if (task.isSuccessful) {
65-
// Sign in success, update UI with the signed-in user's information
66-
Log.d(TAG, "signInAnonymously:success")
67-
val user = auth.currentUser
68-
updateUI(user)
69-
} else {
70-
// If sign in fails, display a message to the user.
71-
Log.w(TAG, "signInAnonymously:failure", task.exception)
72-
Toast.makeText(context, "Authentication failed.",
73-
Toast.LENGTH_SHORT).show()
74-
updateUI(null)
75-
}
76-
77-
hideProgressBar()
78-
}
79-
}
80-
81-
private fun signOut() {
82-
auth.signOut()
83-
updateUI(null)
84-
}
42+
binding.buttonLinkAccount.setOnClickListener {
43+
val email = binding.fieldEmail.text.toString()
44+
val password = binding.fieldPassword.text.toString()
8545

86-
private fun linkAccount() {
87-
// Make sure form is valid
88-
if (!validateLinkForm()) {
89-
return
46+
viewModel.linkAccount(email, password)
9047
}
9148

92-
// Get email and password from the form
93-
val email = binding.fieldEmail.text.toString()
94-
val password = binding.fieldPassword.text.toString()
49+
lifecycleScope.launch {
50+
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
51+
viewModel.uiState.collect { uiState ->
52+
// Handle errors
53+
binding.fieldEmail.error = uiState.emailError
54+
binding.fieldPassword.error = uiState.passwordError
9555

96-
// Create EmailAuthCredential with email and password
97-
val credential = EmailAuthProvider.getCredential(email, password)
56+
// Display texts
57+
binding.anonymousStatusId.text = uiState.userId
58+
binding.anonymousStatusEmail.text = uiState.userEmail
9859

99-
// Link the anonymous user to the email credential
100-
showProgressBar()
101-
102-
auth.currentUser!!.linkWithCredential(credential)
103-
.addOnCompleteListener(requireActivity()) { task ->
104-
if (task.isSuccessful) {
105-
Log.d(TAG, "linkWithCredential:success")
106-
val user = task.result?.user
107-
updateUI(user)
60+
// Toggle progress bar
61+
if (uiState.isProgressBarVisible) {
62+
showProgressBar()
10863
} else {
109-
Log.w(TAG, "linkWithCredential:failure", task.exception)
110-
Toast.makeText(context, "Authentication failed.",
111-
Toast.LENGTH_SHORT).show()
112-
updateUI(null)
64+
hideProgressBar()
11365
}
11466

115-
hideProgressBar()
67+
// Toggle button visibility
68+
binding.buttonAnonymousSignIn.isEnabled = uiState.isSignInEnabled
69+
binding.buttonLinkAccount.isEnabled = uiState.isLinkAccountEnabled
70+
binding.buttonAnonymousSignOut.isEnabled = uiState.isSignOutEnabled
11671
}
117-
}
118-
119-
private fun validateLinkForm(): Boolean {
120-
var valid = true
121-
122-
val email = binding.fieldEmail.text.toString()
123-
if (TextUtils.isEmpty(email)) {
124-
binding.fieldEmail.error = "Required."
125-
valid = false
126-
} else {
127-
binding.fieldEmail.error = null
128-
}
129-
130-
val password = binding.fieldPassword.text.toString()
131-
if (TextUtils.isEmpty(password)) {
132-
binding.fieldPassword.error = "Required."
133-
valid = false
134-
} else {
135-
binding.fieldPassword.error = null
136-
}
137-
138-
return valid
139-
}
140-
141-
private fun updateUI(user: FirebaseUser?) {
142-
hideProgressBar()
143-
val isSignedIn = user != null
144-
145-
// Status text
146-
if (isSignedIn) {
147-
binding.anonymousStatusId.text = getString(R.string.id_fmt, user!!.uid)
148-
binding.anonymousStatusEmail.text = getString(R.string.email_fmt, user.email)
149-
} else {
150-
binding.anonymousStatusId.setText(R.string.signed_out)
151-
binding.anonymousStatusEmail.text = null
72+
}
15273
}
153-
154-
// Button visibility
155-
binding.buttonAnonymousSignIn.isEnabled = !isSignedIn
156-
binding.buttonAnonymousSignOut.isEnabled = isSignedIn
157-
binding.buttonLinkAccount.isEnabled = isSignedIn
15874
}
15975

16076
override fun onDestroyView() {
16177
super.onDestroyView()
16278
_binding = null
16379
}
164-
165-
companion object {
166-
private const val TAG = "AnonymousAuth"
167-
}
16880
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.google.firebase.quickstart.auth.kotlin
2+
3+
import android.util.Log
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.viewModelScope
6+
import com.google.firebase.auth.EmailAuthProvider
7+
import com.google.firebase.auth.FirebaseAuth
8+
import com.google.firebase.auth.FirebaseUser
9+
import com.google.firebase.auth.ktx.auth
10+
import com.google.firebase.ktx.Firebase
11+
import kotlinx.coroutines.flow.MutableStateFlow
12+
import kotlinx.coroutines.flow.StateFlow
13+
import kotlinx.coroutines.flow.update
14+
import kotlinx.coroutines.launch
15+
import kotlinx.coroutines.tasks.await
16+
17+
class AnonymousAuthViewModel(
18+
private val firebaseAuth: FirebaseAuth = Firebase.auth
19+
) : ViewModel() {
20+
21+
private val _uiState = MutableStateFlow(UiState())
22+
val uiState: StateFlow<UiState> = _uiState
23+
24+
data class UiState(
25+
var userId: String? = null,
26+
var userEmail: String? = null,
27+
var isSignInEnabled: Boolean = true,
28+
var isSignOutEnabled: Boolean = false,
29+
var isLinkAccountEnabled: Boolean = false,
30+
var isProgressBarVisible: Boolean = false,
31+
var emailError: String? = null,
32+
var passwordError: String?= null,
33+
)
34+
35+
init {
36+
// Check if there's a user signed in and update UI State accordingly
37+
val firebaseUser = firebaseAuth.currentUser
38+
updateUiState(firebaseUser)
39+
}
40+
41+
fun signInAnonymously() {
42+
viewModelScope.launch {
43+
toggleProgressbar(true)
44+
try {
45+
val authResult = firebaseAuth.signInAnonymously().await()
46+
// Sign in was successful, update UI State with the signed-in user's information
47+
Log.d(TAG, "signInAnonymously:success")
48+
updateUiState(authResult.user)
49+
} catch (e: Exception) {
50+
Log.e(TAG, "signInAnonymously:failure", e)
51+
// TODO(thatfiredev): Display "Authentication failed" snackbar
52+
updateUiState(null)
53+
} finally {
54+
toggleProgressbar(false)
55+
}
56+
}
57+
}
58+
59+
fun linkAccount(email: String, password: String) {
60+
// Make sure entered data is valid
61+
if (!validateEmailAndPassword(email, password)) {
62+
return
63+
}
64+
val credential = EmailAuthProvider.getCredential(email, password)
65+
66+
viewModelScope.launch {
67+
toggleProgressbar(true)
68+
val firebaseUser = firebaseAuth.currentUser!!
69+
70+
try {
71+
val authResult = firebaseUser.linkWithCredential(credential).await()
72+
// Account Link was successful, update UI State with the signed-in user's information
73+
Log.d(TAG, "linkWithCredential:success")
74+
updateUiState(authResult.user)
75+
} catch (e: Exception) {
76+
Log.e(TAG, "linkWithCredential:failure", e)
77+
// TODO(thatfiredev): Display "Authentication failed" snackbar
78+
updateUiState(null)
79+
} finally {
80+
toggleProgressbar(false)
81+
}
82+
}
83+
}
84+
85+
fun signOut() {
86+
firebaseAuth.signOut()
87+
updateUiState(null)
88+
}
89+
90+
private fun updateUiState(user: FirebaseUser?) {
91+
if (user == null) {
92+
_uiState.update { currentUiState ->
93+
currentUiState.copy(
94+
userId = "Signed Out",
95+
userEmail = null,
96+
isSignInEnabled = true,
97+
isSignOutEnabled = false,
98+
isLinkAccountEnabled = false,
99+
isProgressBarVisible = false
100+
)
101+
}
102+
} else {
103+
_uiState.update { currentUiState ->
104+
currentUiState.copy(
105+
userId = "User ID: ${user.uid}",
106+
userEmail = "Email: ${user.email}",
107+
isSignInEnabled = false,
108+
isSignOutEnabled = true,
109+
isLinkAccountEnabled = true,
110+
isProgressBarVisible = false
111+
)
112+
}
113+
}
114+
}
115+
116+
private fun validateEmailAndPassword(email: String, password: String): Boolean {
117+
var isValid = true
118+
119+
if (email.isBlank()) {
120+
_uiState.update { it.copy(emailError = "Required.") }
121+
isValid = false
122+
} else {
123+
_uiState.update { it.copy(emailError = null) }
124+
}
125+
126+
if (password.isBlank()) {
127+
_uiState.update { it.copy(passwordError = "Required.") }
128+
isValid = false
129+
} else {
130+
_uiState.update { it.copy(passwordError = null) }
131+
}
132+
133+
return isValid
134+
}
135+
136+
private fun toggleProgressbar(isVisible: Boolean) {
137+
_uiState.update { it.copy(isProgressBarVisible = isVisible) }
138+
}
139+
140+
companion object {
141+
const val TAG = "AnonymousAuthViewModel"
142+
}
143+
}

0 commit comments

Comments
 (0)