studio - leer datos firebase android
¿Cómo pasar por alto el caché de Firebase para actualizar los datos(en la aplicación de Android)? (5)
En una aplicación de Android que debe funcionar sin conexión la mayor parte del tiempo necesito, cuando está en línea, hacer algunas operaciones sincrónicas para, por ejemplo:
User myUser = MyclientFacade.getUser();
If (myUser.getScore > 10) {
DoSomething()
}
Donde el Usuario es un POJO llenado por Firebase;
El problema ocurre cuando la caché de Firebase está activada
Firebase.getDefaultConfig().setPersistenceEnabled(true);
y el usuario ya está en caché y los datos se actualizan en Firebase DB por un tercero (o incluso por otro dispositivo). De hecho, cuando solicito a Firebase que obtenga el Usuario, obtengo primero los datos del caché y luego un segundo evento de cambio con los datos más recientes del servidor de Firebase, ¡pero ya es demasiado tarde!
Veamos el método sincrónico MyclientFacade.getUser ():
Public User getUser() {
Firebase ref = myFireBaseroot.child("User").child(uid);
ref.keepSynced(true);
/* try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
final CountDownLatch signal = new CountDownLatch(1);
ref.addListenerForSingleValueEvent(new ValueEventListener() {
//ref.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
this.user = dataSnapshot.getValue(User.class);
signal.countDown();
}
@Override
public void onCancelled(FirebaseError firebaseError) {
signal.countDown();
}
});
signal.await(5, TimeUnit.SECONDS);
ref.keepSynced(false);
return this.user;
}
addValueEventListener
el mismo comportamiento si uso addValueEventListener
o addListenerForSingleValueEvent
mezclado con ref.keepSynced
:
Digamos que el valor de puntuación de mi usuario en la memoria caché es 5 y de Firebase DB es 11.
Cuando llamo getUser
el puntaje de 5 (Firebase ask cache primero) así que no llamaré al método doSomething()
.
Si Thread.sleep()
el comentario del código Thread.sleep()
de mi ejemplo, la memoria caché de Firebase tendrá tiempo suficiente para actualizarse y mi getUser
devolverá el valor de puntuación correcto (11).
Entonces, ¿cómo puedo preguntar directamente el último valor directamente desde el servidor y omitir el caché?
Este fue un problema que también me causaba mucho estrés en mi aplicación.
Intenté todo, desde cambiar .addListenerForSingleValueEvent()
a .addValueEventListener()
a tratar de usar creativamente .keepSynced()
a usar un retraso (el método Thread.sleep()
que describiste arriba) y nada realmente funcionó de manera consistente (incluso el Thread.sleep()
método Thread.sleep()
, que no era realmente aceptable en una aplicación de producción, no me dio resultados consistentes).
Entonces, lo que hice fue esto: después de crear un objeto Query y llamar a .keepSynced()
en él, luego procedo a escribir un objeto simulado / token en el nodo que estoy consultando y LUEGO en el oyente de finalización de esa operación, hago los datos recuperación que quiero hacer, después de eliminar el objeto simulado.
Algo como:
MockObject mock = new MockObject();
mock.setIdentifier("delete!");
final Query query = firebase.child("node1").child("node2");
query.keepSynced(true);
firebase.child("node1").child("node2").child("refreshMock")
.setValue(mock, new CompletionListener() {
@Override
public void onComplete(FirebaseError error, Firebase afb) {
query.addListenerForSingleValueEvent(new ValueEventListener() {
public void onDataChange(DataSnapshot data) {
// get identifier and recognise that this data
// shouldn''t be used
// it''s also probably a good idea to remove the
// mock object
// using the removeValue() method in its
// speficic node so
// that the database won''t be saddled with a new
// one in every
// read operation
}
public void onCancelled(FirebaseError error) {
}
});
}
});
}
¡Esto ha funcionado constantemente hasta ahora para mí! (bueno, por un día más o menos, así que tómenlo con un grano de sal). Parece que hacer una operación de escritura antes de leer de alguna manera pasa por alto el caché, lo que tiene sentido. Entonces los datos vuelven frescos.
El único inconveniente es la operación de escritura adicional antes de la operación de lectura, que puede causar un pequeño retraso (obviamente use un objeto pequeño) pero si ese es el precio de los datos siempre nuevos, ¡lo tomaré!
¡Espero que esto ayude!
Intenté ambas soluciones aceptadas e intenté la transacción. La solución de transacción es más limpia y más agradable, pero se llama al éxito OnComplete solo cuando db está en línea, por lo que no se puede cargar desde el caché. Pero puede cancelar la transacción, y luego se llamará a OnComplete cuando esté fuera de línea (con datos en caché).
Creé una función que funcionaba solo si la base de datos obtenía la conexión suficiente 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, creare Android lib y lo publique, 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)
removeListener()
}
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)
removeListener()
}
override fun doTransaction(p0: MutableData?): Transaction.Result {
return Transaction.success(p0)
}
}
Si desea que sea más rápido para desconectarse (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");
}
});
Mi solución fue llamar a Database.database (). IsPersistenceEnabled = true en load up, luego llamar a .keepSynced (true) en los nodos que necesitaba actualizar.
El problema aquí es que si consulta su nodo inmediatamente después de .keepSynced (verdadero), entonces es probable que obtenga el caché en lugar de los datos recientes. Ligeramente cojo pero funcional: demora tu consulta del nodo por un segundo para darle a Firebase un tiempo para obtener los nuevos datos. Obtendrá la caché en su lugar si el usuario está fuera de línea.
Ah, y si es un nodo que no desea mantener actualizado en el fondo para siempre, recuerde llamar a .keepSynced (falso) cuando haya terminado.
Simplemente agregue este código en el método onCreate
en su clase de aplicación . (cambie la referencia de la base de datos)
Ejemplo:
public class MyApplication extendes Application{
@Override
public void onCreate() {
super.onCreate();
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);
}
}
Me funciona bien
Referencia: https://firebase.google.com/docs/database/android/offline-capabilities
Una solución que descubrí es usar el método runTransaction()
Firebase. Esto parece recuperar siempre datos del servidor.
String firebaseUrl = "/some/user/datapath/";
final Firebase firebaseClient = new Firebase(firebaseUrl);
// Use runTransaction to bypass cached DataSnapshot
firebaseClient.runTransaction(new Transaction.Handler() {
@Override
public Transaction.Result doTransaction(MutableData mutableData) {
// Return passed in data
return Transaction.success(mutableData);
}
@Override
public void onComplete(FirebaseError firebaseError, boolean success, DataSnapshot dataSnapshot) {
if (firebaseError != null || !success || dataSnapshot == null) {
System.out.println("Failed to get DataSnapshot");
} else {
System.out.println("Successfully get DataSnapshot");
//handle data here
}
}
});