Skip to content

Commit

Permalink
refactor(facebook): move Firebase calls to ViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
thatfiredev committed Feb 16, 2023
1 parent e8386de commit dbcfb82
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -31,7 +31,7 @@ class FacebookLoginFragment : BaseFragment() {
private val binding: FragmentFacebookBinding
get() = _binding!!

private lateinit var callbackManager: CallbackManager
private val viewModel by viewModels<FacebookLoginViewModel>()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentFacebookBinding.inflate(layoutInflater, container, false)
Expand All @@ -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<LoginResult> {
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
}
}
}

Expand All @@ -130,6 +87,6 @@ class FacebookLoginFragment : BaseFragment() {
}

companion object {
private const val TAG = "FacebookLogin"
private const val TAG = "FacebookLoginFragment"
}
}
Original file line number Diff line number Diff line change
@@ -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> = _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)
}