query para guardar example descargar datos consultas android firebase firebase-database firebase-realtime-database

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"); } });