Solución:
Cómo funciona la persistencia
El cliente de 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 vacían de la memoria.
Si habilita la persistencia del disco en una aplicación de Firebase para Android con:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
El cliente de Firebase mantendrá una copia local (en el disco) de todos los datos que la aplicación ha escuchado recientemente.
¿Qué sucede cuando adjunta un oyente?
Di que tienes lo 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 caché del disco local, el cliente de Firebase invocará onDataChange()
inmediatamente para ese valor de la caché local. Entonces, también iniciará una verificación con el servidor para solicitar actualizaciones del valor. Eso mayo posteriormente invocar onDataChange()
de nuevo si ha habido un cambio de los datos en el servidor desde la última vez que se agregó a la caché.
¿Qué pasa cuando usas addListenerForSingleValueEvent
Cuando agrega un detector de eventos de valor único a la misma ubicación:
ref.addListenerForSingleValueEvent(listener);
El cliente de Firebase (como en la situación anterior) invocará inmediatamente onDataChange()
por el valor de la caché del disco local. Va a no invocar el 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 abordó anteriormente en ¿Cómo funciona la sincronización de Firebase con datos compartidos?
Solución y solución alternativa
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 actualización potencial del servidor.
Como solución alternativa, también puede llamar keepSynced(true)
en las ubicaciones donde usa un detector de eventos de valor único. Esto asegura que los datos se actualicen siempre que cambien, lo que mejora drásticamente la posibilidad de que su oyente de eventos de valor único vea el valor actual.
Entonces tengo una solución funcional para esto. Todo lo que tiene que hacer es usar ValueEventListener y eliminar el oyente después de 0.5 segundos para asegurarse de haber obtenido los datos actualizados para entonces si es necesario. La base de datos en tiempo real tiene una latencia muy buena, por lo que es seguro. Vea el ejemplo de código seguro a continuación;
public class FirebaseController {
private DatabaseReference mRootRef;
private Handler mHandler = new Handler();
private FirebaseController() {
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mRootRef = FirebaseDatabase.getInstance().getReference();
}
public static FirebaseController getInstance() {
if (sInstance == null) {
sInstance = new FirebaseController();
}
return sInstance;
}
Luego, algún método que le hubiera gustado usar “addListenerForSingleEvent”;
public void getTime(final OnTimeRetrievedListener listener) {
DatabaseReference ref = mRootRef.child("serverTime");
ref.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (listener != null) {
// This can be called twice if data changed on server - SO DEAL WITH IT!
listener.onTimeRetrieved(dataSnapshot.getValue(Long.class));
}
// This can be called twice if data changed on server - SO DEAL WITH IT!
removeListenerAfter2(ref, this);
}
@Override
public void onCancelled(DatabaseError databaseError) {
removeListenerAfter2(ref, this);
}
});
}
// ValueEventListener version workaround for addListenerForSingleEvent not working.
private void removeListenerAfter2(DatabaseReference ref, ValueEventListener listener) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
HelperUtil.logE("removing listener", FirebaseController.class);
ref.removeEventListener(listener);
}
}, 500);
}
// ChildEventListener version workaround for addListenerForSingleEvent not working.
private void removeListenerAfter2(DatabaseReference ref, ChildEventListener listener) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
HelperUtil.logE("removing listener", FirebaseController.class);
ref.removeEventListener(listener);
}
}, 500);
}
Incluso si cierran la aplicación antes de que se ejecute el controlador, se eliminará de todos modos. Editar: esto se puede abstraer para realizar un seguimiento de los oyentes agregados y eliminados en un HashMap utilizando la ruta de referencia como clave y la instantánea de datos como valor. Incluso puede ajustar un método fetchData que tiene una bandera booleana para “una vez” si esto es cierto, haría esta solución para obtener datos una vez, de lo contrario, continuaría con normalidad. ¡Eres bienvenido!