Hola, soy Luis y para hoy les traigo un tutorial.
Si tuviera que arriesgarme a adivinar el punto de confusión más frecuente al comenzar con Firebase Authentication, sería el currentUser
API.
Si bien parece bastante simple obtener un objeto que represente al usuario que ha iniciado sesión actualmente, existe una complejidad oculta.
Índice
El estado de autenticación parece binario: nulo o no nulo (¿o lo es?)
Aquí hay un código bastante sencillo en JavaScript que comprueba si un usuario ha iniciado sesión:
const user = firebase.auth().currentUser if (user) // user is signed in, show user data else // user is signed out, show sign-in form
Aquí está el equivalente en Java:
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); if (user != null) // user is signed in, show user data else // user is signed out, show sign-in form
Y Swift:
let user = Auth.auth().currentUser if user != nil // user is signed in, show user data else // user is signed out, show sign-in form
En los tres casos, se utiliza una propiedad de objeto o un método de acceso para comprobar el estado del usuario que ha iniciado sesión actualmente.
Si bien esto funciona a veces, es posible que observe que no funciona como se esperaba cuando la aplicación se inicia en frío o la página web se actualiza. El objeto de usuario actual termina siendo nulo, incluso si el usuario inició sesión anteriormente.
Esto es más confuso dado que la documentación sugiere que el usuario que inició sesión persiste automáticamente de forma predeterminada entre el inicio de la aplicación y la recarga de la página.
Si la información sobre el usuario que inició sesión persiste como se documenta, ¿por qué el objeto sigue siendo nulo? Y aún más confuso, ¿por qué solo en un comienzo en frío como este?
¡Vamos a profundizar en!
El usuario actual es el objeto se obtiene de forma asincrónica
Una cosa a tener en cuenta sobre los SDK de Firebase es que todas sus API son asincrónicas. Esto significa que el SDK no va a bloquear el hilo principal de ejecución para entregar datos; el objeto que contiene al usuario actualmente registrado no es una excepción.
De hecho, Firebase Auth SDK requiere al menos uno, y posiblemente dos pasos, para entregar un objeto de usuario actual válido:
- Verifique y cargue el token de ID de usuario persistente desde el disco.
- Actualice el token de ID del usuario si es necesario (cada hora).
Ambos son operaciones de bloqueo. La lectura desde el disco obviamente requiere E / S
, que normalmente es rápida, pero no se garantiza que sea así.
Y, actualizar el token de ID requiere E / S
de red para actualizar el token de ID, si no lo ha sido en la última hora.
Dado que estas operaciones se bloquean indefinidamente, el SDK no puede permitir que ninguna de ellas suceda en el hilo principal para que esté disponible a medida que se carga la aplicación.
Eso correría el riesgo de ralentizar el tiempo de inicio de la aplicación o incluso hacer que se cuelgue por completo. Eso es, por supuesto, muy malo para sus usuarios.
currentUser
solo devuelve dos valores posibles: nulo (sin sesión) y no nulo (con sesión).
Por lo tanto, le da la impresión de que el estado actual del estado de inicio de sesión es binario, ya sea uno u otro. Pero esta no es realmente la forma en que funciona.
¡El estado de autenticación es en realidad trinario!
La forma en que me gusta pensar es así: el estado actual del usuario es en realidad trinario. Yo llamo a estos estados:
- Desconocido.
- Definitivamente desconectado.
- Definitivamente registrado.
La API currentUser
solo expone dos tipos de valores: nulos o no nulos, por lo que no puede distinguir la diferencia entre los estados 1
y 2
. Un valor nulo podría significar cualquiera de los dos, ¡y no tenemos forma de verificar esto inmediatamente!
Esto es problemático porque su aplicación probablemente necesite una lógica que sea así (en pseudocódigo):
switch (userState) case unknown: // show a splash screen or loading indicator case definitelySignedOut: // show a sign in form case definitelySignedIn: // show main screen
Mostrar un indicador de carga en el estado desconocido es muy importante, porque simplemente no sabemos cuánto tiempo tardará el SDK en cargar y actualizar ese token. Se debe indicar al usuario que espere.
Entonces, ¿Cuál es la solución?
Utilice un observador de estado de autenticación (también conocido como oyente de estado de autenticación)
El SDK le permite recibir una devolución de llamada asincrónica cuando cambia el estado de autenticación del usuario. Se ve así en JavaScript, con devoluciones de llamada similares para Android e iOS:
firebase.auth().onAuthStateChanged(user => if (user) // User is signed in. else // User is signed out. )
El problema aquí sigue siendo que el objeto de usuario entregado a la función de devolución de llamada sigue siendo nulo o no nulo.
Pero necesitamos una forma de distinguir entre «desconocido» y «definitivamente cerrado». Lo anotaré más claramente con algunos mensajes de registro:
console.log("state = unknown (until the callback is invoked)") firebase.auth().onAuthStateChanged(user => if (user) console.log("state = definitely signed in") else console.log("state = definitely signed out") )
Tenga en cuenta que el estado de autenticación del usuario es desconocido solo antes de que se invoque la devolución de llamada por primera vez, por lo que debe configurar su interfaz de usuario para mostrar esa pantalla de inicio o indicador de carga antes de configurar este observador.
Y debe configurarlo lo antes posible después del lanzamiento de su aplicación. Después de eso, el usuario solo puede alternar entre iniciar sesión y cerrar sesión, mientras se esté ejecutando el proceso de la aplicación.
En un arranque en frío, tenemos que hacer todo esto de nuevo, comenzando de nuevo desde el estado desconocido.
onAuthStateChanged
en JavaScript, addAuthStateListener
en Java, y addDidStateChangeListener
en Swift.
Definitivamente siempre debe usar estas API de escucha en lugar de las API de usuario actual síncronas que mostré en la parte superior de esta publicación.
En mi opinión, es bastante lamentable que el SDK no exponga este estado trinario por diseño, ya que genera toda esa confusión sobre cómo funciona la API de usuario actual.
Pero la buena noticia es que puede crear su propia API para envolver Firebase Auth. El resto de este artículo debería darle algunas ideas.
Para web: use observables RxJS
Aquí hay un código TypeScript que ilustra cómo componer un nuevo observable que emite cada estado.
enum UserStatus { Unknown, SignedIn, SignedOut }class UserState { readonly status: UserStatus readonly value: firebase.User | undefined | null constructor(value: firebase.User | undefined | null) { this.status = value === undefined ? UserStatus.Unknown : value === null ? UserStatus.SignedOut : UserStatus.SignedIn this.value = value } }const userBehaviorSubject = new BehaviorSubject<UserState>(new UserState(undefined))const authStateObserver = (user: firebase.User | null) => { if (user) { userBehaviorSubject.next(new UserState(user)) } else { userBehaviorSubject.next(new UserState(null)) } }firebase.auth().onAuthStateChanged(authStateObserver)
Este código expone un objeto UserState
a través del BehaviorSubject
llamado userBehaviorSubject
. Tiene una enumeración que puede usar para verificar fácilmente el estado y encontrar el objeto de usuario.
Use esto para determinar cómo renderizar su página web a medida que el estado de autenticación cambia con el tiempo.
Tenga en cuenta que el valor inicial de Subject
se establece en desconocido a través del valor indefinido, luego cambia con el tiempo con las actualizaciones del Auth SDK
. Puedes usarlo así:
const userStateObserver = async (userState: UserState) => { switch (userState.status) { case UserStatus.SignedIn: console.log(`${user.uid} signed in`) break case UserStatus.SignedOut: console.log("User signed out") break default: console.log("User status unknown") break } }userBehaviorSubject.subscribe(this.userStateObserver)
Para Android: use un LiveData
LiveData
es la forma preferida en Android para exponer valores que pueden cambiar con el tiempo. Aquí hay algunos Kotlin que puede usar para construir un LiveData
que exponga el estado de usuario de Firebase Auth
:
sealed class FirebaseAuthUserState data class UserSignedIn(val user: FirebaseUser) : FirebaseAuthUserState() object UserSignedOut : FirebaseAuthUserState() object UserUnknown : FirebaseAuthUserState()@MainThread fun FirebaseAuth.newFirebaseAuthStateLiveData( context: CoroutineContext = EmptyCoroutineContext ): LiveData<FirebaseAuthUserState> { val ld = FirebaseAuthStateLiveData(this) return liveData(context) { emitSource(ld) } }class FirebaseAuthStateLiveData(private val auth: FirebaseAuth) : LiveData<FirebaseAuthUserState>() {private val authStateListener = MyAuthStateListener() init { value = UserUnknown } override fun onActive() { auth.addAuthStateListener(authStateListener) } override fun onInactive() { auth.removeAuthStateListener(authStateListener) } private inner class MyAuthStateListener : AuthStateListener { override fun onAuthStateChanged(auth: FirebaseAuth) { val user = auth.currentUser value = if (user != null) { UserSignedIn(user) } else { UserSignedOut } } }}
Este código crea una función de extensión FirebaseAuth
que le permite crear un nuevo LiveData
que emite una clase sellada con los tres estados posibles.
Este LiveData
podría ser un singleton en su aplicación que cualquier componente puede usar para rastrear el estado del usuario.
Tenga en cuenta que el valor inicial en el constructor es UserUnknown
, luego cambia con el tiempo con las actualizaciones del Auth SDK
. Puedes usarlo así:
val myUserStateObserver = Observer<FirebaseAuthUserState> { userState -> when (userState) { is UserSignedOut -> // your code is UserSignedIn -> // your code is UserUnknown -> // your code } }val authStateLiveData = authRepo.authStateLiveData authStateLiveData.observeForever(myUserStateObserver)
Gracias por leer este tutorial.
Añadir comentario