From e8386decf9a430948f73fee0aebe03b0f6c57c73 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Thu, 16 Feb 2023 19:36:42 +0000 Subject: [PATCH] refactor(custom): move Firebase calls to ViewModel --- .../auth/kotlin/CustomAuthFragment.kt | 100 ++++++------------ .../auth/kotlin/CustomAuthViewModel.kt | 81 ++++++++++++++ 2 files changed, 112 insertions(+), 69 deletions(-) create mode 100644 auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthViewModel.kt diff --git a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthFragment.kt b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthFragment.kt index 0621e0ba6..f351fec29 100644 --- a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthFragment.kt +++ b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthFragment.kt @@ -5,31 +5,30 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.fragment.app.Fragment -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseUser -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.auth.R +import androidx.fragment.app.viewModels +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.google.firebase.quickstart.auth.databinding.FragmentCustomBinding +import kotlinx.coroutines.launch /** * Demonstrate Firebase Authentication using a custom minted token. For more information, see: * https://firebase.google.com/docs/auth/android/custom-auth */ class CustomAuthFragment : Fragment() { - - private lateinit var auth: FirebaseAuth - private var _binding: FragmentCustomBinding? = null private val binding: FragmentCustomBinding get() = _binding!! - private var customToken: String? = null + private val viewModel by viewModels() + private lateinit var tokenReceiver: TokenBroadcastReceiver - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentCustomBinding.inflate(inflater, container, false) return binding.root } @@ -38,76 +37,39 @@ class CustomAuthFragment : Fragment() { super.onViewCreated(view, savedInstanceState) // Button click listeners - binding.buttonSignIn.setOnClickListener { startSignIn() } + binding.buttonSignIn.setOnClickListener { viewModel.startSignIn() } // Create token receiver (for demo purposes only) tokenReceiver = object : TokenBroadcastReceiver() { override fun onNewToken(token: String?) { Log.d(TAG, "onNewToken:$token") - setCustomToken(token.toString()) + viewModel.setCustomToken(token) } } - // Initialize Firebase Auth - auth = Firebase.auth - } - - override fun onStart() { - super.onStart() - // Check if user is signed in (non-null) and update UI accordingly. - val currentUser = auth.currentUser - updateUI(currentUser) - } - - override fun onResume() { - super.onResume() - requireActivity().registerReceiver(tokenReceiver, TokenBroadcastReceiver.filter) - } - - override fun onPause() { - super.onPause() - requireActivity().unregisterReceiver(tokenReceiver) - } - - private fun startSignIn() { - // Initiate sign in with custom token - customToken?.let { - auth.signInWithCustomToken(it) - .addOnCompleteListener(requireActivity()) { task -> - if (task.isSuccessful) { - // Sign in success, update UI with the signed-in user's information - Log.d(TAG, "signInWithCustomToken:success") - val user = auth.currentUser - updateUI(user) - } else { - // If sign in fails, display a message to the user. - Log.w(TAG, "signInWithCustomToken:failure", task.exception) - Toast.makeText(context, "Authentication failed.", - Toast.LENGTH_SHORT).show() - updateUI(null) - } - } - } - } + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + requireActivity().registerReceiver(tokenReceiver, TokenBroadcastReceiver.filter) + } - private fun updateUI(user: FirebaseUser?) { - if (user != null) { - binding.textSignInStatus.text = getString(R.string.custom_auth_signin_status_user, user.uid) - } else { - binding.textSignInStatus.text = getString(R.string.custom_auth_signin_status_failed) + override fun onPause(owner: LifecycleOwner) { + super.onPause(owner) + requireActivity().unregisterReceiver(tokenReceiver) + } + }) + + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { uiState -> + binding.buttonSignIn.isEnabled = uiState.isSignInEnabled + binding.textSignInStatus.text = uiState.signInStatus + binding.textTokenStatus.text = uiState.tokenStatus + } + } } } - private fun setCustomToken(token: String) { - customToken = token - - val status = "Token:$customToken" - - // Enable/disable sign-in button and show the token - binding.buttonSignIn.isEnabled = true - binding.textTokenStatus.text = status - } - override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthViewModel.kt b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthViewModel.kt new file mode 100644 index 000000000..fe756f63e --- /dev/null +++ b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthViewModel.kt @@ -0,0 +1,81 @@ +package com.google.firebase.quickstart.auth.kotlin + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseUser +import com.google.firebase.auth.ktx.auth +import com.google.firebase.ktx.Firebase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +class CustomAuthViewModel( + private val firebaseAuth: FirebaseAuth = Firebase.auth +) : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow = _uiState + + private var customToken: String? = null + + data class UiState( + var signInStatus: String = "", + var tokenStatus: String = "Token:null", + var isSignInEnabled: Boolean = false, + ) + + init { + // Check if user is signed in (non-null) and update UI accordingly. + val firebaseUser = firebaseAuth.currentUser + updateUiState(firebaseUser) + } + + fun setCustomToken(customToken: String?) { + this.customToken = customToken + + // Enable/disable sign-in button and show the token + _uiState.update { it.copy(isSignInEnabled = true, tokenStatus = "Token:$customToken") } + } + + fun startSignIn() { + customToken?.let { token -> + // Initiate sign in with custom token + viewModelScope.launch { + try { + val authResult = firebaseAuth.signInWithCustomToken(token).await() + // Sign in success, update UI with the signed-in user's information + Log.d(TAG, "signInWithCustomToken:success") + updateUiState(authResult.user) + } catch (e: Exception) { + // If sign in fails, display a message to the user. + Log.w(TAG, "signInWithCustomToken:failure", e) + // TODO(thatfiredev): Show "Authentication failed" snackbar + updateUiState(null) + } + } + } + } + + private fun updateUiState(user: FirebaseUser?) { + if (user != null) { + _uiState.update { currentUiState -> + currentUiState.copy( + signInStatus = "User ID: ${user.uid}" + ) + } + } else { + _uiState.update { currentUiState -> + currentUiState.copy( + signInStatus = "Error: sign in failed" + ) + } + } + } + + companion object { + private const val TAG = "CustomAuthViewModel" + } +} \ No newline at end of file