From dbcfb82593bd339993c1ece1894ff4c2c32c4f4a Mon Sep 17 00:00:00 2001 From: rosariopf Date: Thu, 16 Feb 2023 19:53:53 +0000 Subject: [PATCH] refactor(facebook): move Firebase calls to ViewModel --- .../auth/kotlin/FacebookLoginFragment.kt | 89 +++++------------ .../auth/kotlin/FacebookLoginViewModel.kt | 99 +++++++++++++++++++ 2 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginViewModel.kt diff --git a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginFragment.kt b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginFragment.kt index 25147e24aa..8eccded9db 100644 --- a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginFragment.kt +++ b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginFragment.kt @@ -5,20 +5,20 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast -import com.facebook.AccessToken +import androidx.core.view.isGone +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.facebook.CallbackManager import com.facebook.FacebookCallback import com.facebook.FacebookException -import com.facebook.login.LoginManager import com.facebook.login.LoginResult -import com.google.firebase.auth.FacebookAuthProvider 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 com.google.firebase.quickstart.auth.databinding.FragmentFacebookBinding +import kotlinx.coroutines.launch /** * Demonstrate Firebase Authentication using a Facebook access token. @@ -31,7 +31,7 @@ class FacebookLoginFragment : BaseFragment() { private val binding: FragmentFacebookBinding get() = _binding!! - private lateinit var callbackManager: CallbackManager + private val viewModel by viewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentFacebookBinding.inflate(layoutInflater, container, false) @@ -42,85 +42,42 @@ class FacebookLoginFragment : BaseFragment() { super.onViewCreated(view, savedInstanceState) setProgressBar(binding.progressBar) - binding.buttonFacebookSignout.setOnClickListener { signOut() } + binding.buttonFacebookSignout.setOnClickListener { viewModel.signOut() } // Initialize Firebase Auth auth = Firebase.auth // Initialize Facebook Login button - callbackManager = CallbackManager.Factory.create() + val callbackManager = CallbackManager.Factory.create() binding.buttonFacebookLogin.setPermissions("email", "public_profile") binding.buttonFacebookLogin.registerCallback(callbackManager, object : FacebookCallback { - override fun onSuccess(loginResult: LoginResult) { - Log.d(TAG, "facebook:onSuccess:$loginResult") - handleFacebookAccessToken(loginResult.accessToken) + override fun onSuccess(result: LoginResult) { + Log.d(TAG, "facebook:onSuccess:$result") + viewModel.handleFacebookAccessToken(result.accessToken) } override fun onCancel() { Log.d(TAG, "facebook:onCancel") - updateUI(null) + viewModel.showInitialState() } override fun onError(error: FacebookException) { Log.d(TAG, "facebook:onError", error) - updateUI(null) + viewModel.showInitialState() } }) - } - override fun onStart() { - super.onStart() - // Check if user is signed in (non-null) and update UI accordingly. - val currentUser = auth.currentUser - updateUI(currentUser) - } + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { uiState -> + binding.status.text = uiState.status + binding.detail.text = uiState.detail - private fun handleFacebookAccessToken(token: AccessToken) { - Log.d(TAG, "handleFacebookAccessToken:$token") - showProgressBar() - - val credential = FacebookAuthProvider.getCredential(token.token) - auth.signInWithCredential(credential) - .addOnCompleteListener(requireActivity()) { task -> - if (task.isSuccessful) { - // Sign in success, update UI with the signed-in user's information - Log.d(TAG, "signInWithCredential:success") - val user = auth.currentUser - updateUI(user) - } else { - // If sign in fails, display a message to the user. - Log.w(TAG, "signInWithCredential:failure", task.exception) - Toast.makeText(context, "Authentication failed.", - Toast.LENGTH_SHORT).show() - updateUI(null) - } - - hideProgressBar() + binding.buttonFacebookLogin.isGone = !uiState.isSignInVisible + binding.buttonFacebookSignout.isGone = uiState.isSignInVisible } - } - - fun signOut() { - auth.signOut() - LoginManager.getInstance().logOut() - - updateUI(null) - } - - private fun updateUI(user: FirebaseUser?) { - hideProgressBar() - if (user != null) { - binding.status.text = getString(R.string.facebook_status_fmt, user.displayName) - binding.detail.text = getString(R.string.firebase_status_fmt, user.uid) - - binding.buttonFacebookLogin.visibility = View.GONE - binding.buttonFacebookSignout.visibility = View.VISIBLE - } else { - binding.status.setText(R.string.signed_out) - binding.detail.text = null - - binding.buttonFacebookLogin.visibility = View.VISIBLE - binding.buttonFacebookSignout.visibility = View.GONE + } } } @@ -130,6 +87,6 @@ class FacebookLoginFragment : BaseFragment() { } companion object { - private const val TAG = "FacebookLogin" + private const val TAG = "FacebookLoginFragment" } } diff --git a/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginViewModel.kt b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginViewModel.kt new file mode 100644 index 0000000000..6a1f8015a2 --- /dev/null +++ b/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginViewModel.kt @@ -0,0 +1,99 @@ +package com.google.firebase.quickstart.auth.kotlin + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.facebook.AccessToken +import com.facebook.login.LoginManager +import com.google.firebase.auth.FacebookAuthProvider +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 FacebookLoginViewModel( + private val firebaseAuth: FirebaseAuth = Firebase.auth +) : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow = _uiState + + data class UiState( + var status: String = "", + var detail: String? = null, + var isSignInVisible: Boolean = true, + var isProgressBarVisible: Boolean = false + ) + + init { + // Check if user is signed in (non-null) and update UI accordingly. + val firebaseUser = firebaseAuth.currentUser + updateUiState(firebaseUser) + } + + fun handleFacebookAccessToken(token: AccessToken) { + Log.d(TAG, "handleFacebookAccessToken:$token") + toggleProgressbar(isVisible = true) + + val credential = FacebookAuthProvider.getCredential(token.token) + viewModelScope.launch { + try { + val authResult = firebaseAuth.signInWithCredential(credential).await() + // Sign in success, update UI with the signed-in user's information + Log.d(TAG, "signInWithCredential:success") + updateUiState(authResult.user) + } catch (e: Exception) { + // If sign in fails, display a message to the user. + Log.w(TAG, "signInWithCredential:failure", e) + // TODO(thatfiredev): Show snackbar "Authentication failed." + updateUiState(null) + } finally { + toggleProgressbar(isVisible = false) + } + } + } + + fun showInitialState() { + updateUiState(null) + } + + fun signOut() { + firebaseAuth.signOut() + LoginManager.getInstance().logOut() + updateUiState(null) + } + + fun updateUiState(user: FirebaseUser?) { + if (user != null) { + _uiState.update { currentUiState -> + currentUiState.copy( + status = "Facebook User: ${user.displayName}", + detail = "Firebase UID: ${user.uid}", + isSignInVisible = false, + isProgressBarVisible = false + ) + } + } else { + _uiState.update { currentUiState -> + currentUiState.copy( + status = "Signed out", + detail = null, + isSignInVisible = true, + isProgressBarVisible = false + ) + } + } + } + + private fun toggleProgressbar(isVisible: Boolean) { + _uiState.update { it.copy(isProgressBarVisible = isVisible) } + } + + companion object { + const val TAG = "FacebookLoginViewModel" + } +} \ No newline at end of file