Skip to content

Commit

Permalink
refactor(custom): 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 32022e3 commit e8386de
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<CustomAuthViewModel>()

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
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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> = _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"
}
}

0 comments on commit e8386de

Please sign in to comment.