Ejemplo de implementación de autentificación firebase auth compaginando viewmodel
Last active
August 30, 2022 13:50
-
-
Save webserveis/ffccd193e2938f27606cc9b03698bf1c to your computer and use it in GitHub Desktop.
Firebase Auth ViewModel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, {}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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