Skip to content

Instantly share code, notes, and snippets.

@webserveis
Last active August 30, 2022 13:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webserveis/ffccd193e2938f27606cc9b03698bf1c to your computer and use it in GitHub Desktop.
Save webserveis/ffccd193e2938f27606cc9b03698bf1c to your computer and use it in GitHub Desktop.
Firebase Auth ViewModel

Firebase Auth + ViewModel

Ejemplo de implementación de autentificación firebase auth compaginando viewmodel

sealed class AuthState {
object Idle : AuthState()
object Authenticating : AuthState()
class Authenticated(val user: FirebaseUser? = null) : AuthState()
object Unauthenticated : AuthState()
class AuthError(val message: String? = null) : AuthState()
}
class AuthViewModel(private val application: Application) : ViewModel() {
companion object {
private const val TAG = "AuthViewModel"
}
sealed class Event {
object Authenticated : Event()
object AuthenticateCanceled : Event()
object AuthenticateInvalid : Event()
//data class ShowToast(val text: String) : Event()
}
private val _event = Channel<Event>(Channel.BUFFERED)
val eventsFlow = _event.receiveAsFlow()
private val _authState: MutableStateFlow<AuthState> = MutableStateFlow(AuthState.Idle)
val authState: StateFlow<AuthState> = _authState
private var mGoogleSignInClient: GoogleSignInClient
private val firebaseAuth by lazy {
FirebaseAuth.getInstance()
}
//var account = GoogleSignIn.getLastSignedInAccount(application)
init {
val mGoogleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(application.getString(R.string.default_web_client_id))
.requestEmail() //Specifies that email info is requested by your application.
.requestProfile() //Specifies that user's profile info is requested by your application.
.build()
mGoogleSignInClient = GoogleSignIn.getClient(application, mGoogleSignInOptions)
}
fun getAuthIntent(): Intent {
viewModelScope.launch {
_authState.emit(AuthState.Authenticating)
}
return mGoogleSignInClient.signInIntent
}
fun onSignInResult(result: ActivityResult) {
if (result.resultCode == AppCompatActivity.RESULT_OK) {
val data = result.data
val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account)
} catch (e: ApiException) {
//Toast.makeText(application, "Google sign in failed:(", Toast.LENGTH_LONG).show()
viewModelScope.launch {
_event.send(Event.AuthenticateInvalid)
}
}
/* // Successfully signed in
val user = FirebaseAuth.getInstance().currentUser
Toast.makeText(requireContext(), "Auth with $user", Toast.LENGTH_SHORT).show()*/
} else {
// Sign in failed. If response is null the user canceled the
// sign-in flow using the back button. Otherwise check
// response.getError().getErrorCode() and handle the error.
// ...
//Toast.makeText(application, "User canceled Auth Flow", Toast.LENGTH_SHORT).show()
viewModelScope.launch {
_event.send(Event.AuthenticateCanceled)
_authState.emit(AuthState.AuthError())
}
}
}
private fun firebaseAuthWithGoogle(acct: GoogleSignInAccount) {
val credential = GoogleAuthProvider.getCredential(acct.idToken, null)
firebaseAuth.signInWithCredential(credential).addOnCompleteListener {
if (it.isSuccessful) {
viewModelScope.launch {
_event.send(Event.Authenticated)
}
} else {
//Toast.makeText(application, "Google sign in failed:(", Toast.LENGTH_LONG).show()
viewModelScope.launch {
_event.send(Event.AuthenticateInvalid)
}
}
}
}
fun checkAuth() {
viewModelScope.launch {
if (firebaseAuth.currentUser == null) {
_authState.emit(AuthState.Unauthenticated)
} else {
_authState.emit(AuthState.Authenticated(firebaseAuth.currentUser))
}
}
}
fun getCurrentUser(): FirebaseUser? {
return firebaseAuth.currentUser
}
fun singOut() {
mGoogleSignInClient.signOut().addOnCompleteListener {
Toast.makeText(application, "Google sign Out", Toast.LENGTH_LONG).show()
}
firebaseAuth.signOut()
}
}
@Suppress("UNCHECKED_CAST")
class AuthViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AuthViewModel::class.java)) {
return AuthViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
class FlowObserver<T> (
lifecycleOwner: LifecycleOwner,
private val flow: Flow<T>,
private val collector: suspend (T) -> Unit
) {
private var job: Job? = null
init {
lifecycleOwner.lifecycle.addObserver(LifecycleEventObserver {
source: LifecycleOwner, event: Lifecycle.Event ->
when (event) {
Lifecycle.Event.ON_START -> {
job = source.lifecycleScope.launch {
flow.collect { collector(it) }
}
}
Lifecycle.Event.ON_STOP -> {
job?.cancel()
job = null
}
else -> { }
}
})
}
}
inline fun <reified T> Flow<T>.observeOnLifecycle(
lifecycleOwner: LifecycleOwner,
noinline collector: suspend (T) -> Unit
) = FlowObserver(lifecycleOwner, this, collector)
inline fun <reified T> Flow<T>.observeInLifecycle(
lifecycleOwner: LifecycleOwner
) = FlowObserver(lifecycleOwner, this, {})
class LoginFragment : Fragment() {
companion object {
private const val TAG = "LoginScreen"
}
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private val mViewModel by activityViewModels<AuthViewModel>() {
AuthViewModelFactory(requireActivity().application)
}
private val signInLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { res ->
mViewModel.onSignInResult(res)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initEventObservers()
initObservers()
binding.Signin.setOnClickListener {
it.isEnabled = false
launchSignIn()
}
val tosPrivacy = binding.tosPrivacy
tosPrivacy.text = getString(R.string.sign_in_tos_privacy).toHtml()
tosPrivacy.handleUrlClicks {
Toast.makeText(requireContext(), "click on $it", Toast.LENGTH_SHORT).show()
}
}
private fun launchSignIn() {
signInLauncher.launch(mViewModel.getAuthIntent())
}
private fun initEventObservers() {
mViewModel.eventsFlow
.onEach {
when (it) {
AuthViewModel.Event.Authenticated -> {
Toast.makeText(requireContext(), "Authenticated!", Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_loginFragment_to_authWelcomeFragment)
}
AuthViewModel.Event.AuthenticateCanceled -> {
binding.Signin.shake()
binding.Signin.isEnabled = true
}
AuthViewModel.Event.AuthenticateInvalid -> {
binding.Signin.shake()
binding.Signin.isEnabled = true
}
}
}
.observeInLifecycle(viewLifecycleOwner)
}
private fun initObservers() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mViewModel.authState.collect {
when (it) {
is AuthState.AuthError -> {
Log.d(TAG, "initObservers: AuthState.AuthError ${it.message}")
}
is AuthState.Idle -> {
Log.d(TAG, "initObservers: AuthState.Idle")
}
is AuthState.Authenticating -> {
Log.d(TAG, "initObservers: AuthState.Authenticating")
}
is AuthState.Authenticated -> {
Log.d(TAG, "initObservers: AuthState.Authenticated" + it.user)
}
is AuthState.Unauthenticated -> {
Log.d(TAG, "initObservers: AuthState.Unauthenticated")
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment