android - para - Capacidades sin conexión de Firebase y addListenerForSingleValueEvent
guardar datos en firebase android (3)
Cada vez que uso
addListenerForSingleValueEvent
con
setPersistenceEnabled(true)
, solo logro obtener una copia local fuera de línea de
DataSnapshot
y
NO
la
DataSnapshot
actualizada del servidor.
Sin embargo, si uso
addValueEventListener
con
setPersistenceEnabled(true)
, puedo obtener la última copia de
DataSnapshot
del servidor.
¿Es esto normal para
addListenerForSingleValueEvent
ya que solo busca
DataSnapshot
localmente (fuera de línea) y elimina su escucha después de recuperar con éxito
DataSnapshot
UNA VEZ
(fuera de línea o en línea)?
Cómo funciona la persistencia
El cliente Firebase mantiene una copia de todos los datos que está escuchando activamente en la memoria. Una vez que el último oyente se desconecta, los datos se eliminan de la memoria.
Si habilita la persistencia de disco en una aplicación de Android Firebase con:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
El cliente Firebase mantendrá una copia local (en el disco) de todos los datos que la aplicación ha escuchado recientemente.
¿Qué sucede cuando adjuntas un oyente?
Digamos que tiene el siguiente
ValueEventListener
:
ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
@Override
public void onCancelled(FirebaseError firebaseError) {
// No-op
}
};
Cuando agrega un
ValueEventListener
a una ubicación:
ref.addValueEventListener(listener);
// OR
ref.addListenerForSingleValueEvent(listener);
Si el valor de la ubicación está en la memoria caché del disco local, el cliente Firebase invocará
onDataChange()
inmediatamente para ese valor desde la memoria caché local.
If también iniciará una verificación con el servidor, para solicitar cualquier actualización del valor.
Posteriormente,
puede
invocar
onDataChange()
nuevamente si ha habido un cambio en los datos del servidor desde la última vez que se agregó al caché.
¿Qué sucede cuando usas
addListenerForSingleValueEvent
Cuando agrega un detector de eventos de valor único a la misma ubicación:
ref.addListenerForSingleValueEvent(listener);
El cliente Firebase (como en la situación anterior) invocará inmediatamente a
onDataChange()
para obtener el valor del caché del disco local.
No invocará
onDataChange()
más veces, incluso si el valor en el servidor resulta ser diferente.
Tenga en cuenta que los datos actualizados aún se solicitarán y se devolverán en solicitudes posteriores.
Esto se cubrió anteriormente en ¿Cómo funciona la sincronización de Firebase, con datos compartidos?
Solución y solución
La mejor solución es usar
addValueEventListener()
, en lugar de un detector de eventos de valor único.
Un oyente de valor regular obtendrá tanto el evento local inmediato como la posible actualización del servidor.
Como solución alternativa, también puede
llamar a
keepSynced(true)
en las ubicaciones donde utiliza un detector de eventos de valor único.
Esto garantiza que los datos se actualicen cada vez que cambien, lo que mejora drásticamente la posibilidad de que su escucha de eventos de valor único vea el valor actual.
Cuando workinkg con persistencia habilitada, conté las veces que el oyente recibió una llamada a onDataChange () y me detuve para escuchar 2 veces. Funcionó para mí, tal vez ayuda:
private int timesRead;
private ValueEventListener listener;
private DatabaseReference ref;
private void readFB() {
timesRead = 0;
if (ref == null) {
ref = mFBDatabase.child("URL");
}
if (listener == null) {
listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
//process dataSnapshot
timesRead++;
if (timesRead == 2) {
ref.removeEventListener(listener);
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
}
ref.removeEventListener(listener);
ref.addValueEventListener(listener);
}
Puede crear una transacción y cancelarla, luego se llamará a onComplete cuando esté en línea (datos en línea) o fuera de línea (datos en caché)
Anteriormente creé una función que funcionaba solo si la base de datos tenía una conexión lo suficientemente grande como para sincronizar. Solucioné el problema agregando tiempo de espera. Trabajaré en esto y probaré si esto funciona. Tal vez en el futuro, cuando tenga tiempo libre, crearé Android lib y lo publicaré, pero para entonces es el código en kotlin:
/**
* @param databaseReference reference to parent database node
* @param callback callback with mutable list which returns list of objects and boolean if data is from cache
* @param timeOutInMillis if not set it will wait all the time to get data online. If set - when timeout occurs it will send data from cache if exists
*/
fun readChildrenOnlineElseLocal(databaseReference: DatabaseReference, callback: ((mutableList: MutableList<@kotlin.UnsafeVariance T>, isDataFromCache: Boolean) -> Unit), timeOutInMillis: Long? = null) {
var countDownTimer: CountDownTimer? = null
val transactionHandlerAbort = object : Transaction.Handler { //for cache load
override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) {
val listOfObjects = ArrayList<T>()
data?.let {
data.children.forEach {
val child = it.getValue(aClass)
child?.let {
listOfObjects.add(child)
}
}
}
callback.invoke(listOfObjects, true)
}
override fun doTransaction(p0: MutableData?): Transaction.Result {
return Transaction.abort()
}
}
val transactionHandlerSuccess = object : Transaction.Handler { //for online load
override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) {
countDownTimer?.cancel()
val listOfObjects = ArrayList<T>()
data?.let {
data.children.forEach {
val child = it.getValue(aClass)
child?.let {
listOfObjects.add(child)
}
}
}
callback.invoke(listOfObjects, false)
}
override fun doTransaction(p0: MutableData?): Transaction.Result {
return Transaction.success(p0)
}
}
En el código, si se establece el tiempo de espera, configuro el temporizador que llamará a la transacción con aborto.
Esta transacción se llamará incluso cuando esté fuera de línea y proporcionará datos en línea o en caché (en esta función hay muchas posibilidades de que estos datos se almacenen en caché).
Entonces llamo transacción con éxito.
OnComplete
se llamará SOLAMENTE si recibimos respuesta de la base de datos de Firebase.
Ahora podemos cancelar el temporizador (si no es nulo) y enviar datos a la devolución de llamada.
Esta implementación hace que el desarrollador esté 99% seguro de que los datos provienen del caché o están en línea.
Si desea hacerlo más rápido sin conexión (para no esperar estúpidamente con el tiempo de espera cuando obviamente la base de datos no está conectada), verifique si la base de datos está conectada antes de usar la función anterior:
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
System.out.println("connected");
} else {
System.out.println("not connected");
}
}
@Override
public void onCancelled(DatabaseError error) {
System.err.println("Listener was cancelled");
}
});