每当我将addListenerForSingleValueEvent
与setPersistenceEnabled(true)
一起使用时,我只能设法获取DataSnapshot
和不更新的DataSnapshot
的本地离线副本服务器。
但是,如果我将addValueEventListener
与setPersistenceEnabled(true)
一起使用,我可以从服务器获取DataSnapshot
的最新副本。
这是addListenerForSingleValueEvent
的正常现象,因为它仅在本地(离线)搜索DataSnapshot
并在成功检索DataSnapshot
ONCE 后离线或移除其监听器(离线或在线) )?
答案 0 :(得分:69)
Firebase客户端会保留您在内存中主动侦听的所有数据的副本。一旦最后一个侦听器断开连接,数据将从内存中刷新。
如果您使用以下命令在Firebase Android应用程序中启用磁盘持久性:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
Firebase客户端将保留应用最近收听的所有数据的本地副本(在磁盘上)。
假设您有以下ValueEventListener
:
ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
}
@Override
public void onCancelled(FirebaseError firebaseError) {
// No-op
}
};
将ValueEventListener
添加到某个位置时:
ref.addValueEventListener(listener);
// OR
ref.addListenerForSingleValueEvent(listener);
如果该位置的值位于本地磁盘缓存中,Firebase客户端将立即从本地缓存中为该值调用onDataChange()
。如果还将启动与服务器的检查,则要求对值进行任何更新。如果服务器上的数据自上次添加到缓存后发生了更改,可能随后再次调用onDataChange()
。
addListenerForSingleValueEvent
将单个值事件侦听器添加到同一位置时:
ref.addListenerForSingleValueEvent(listener);
Firebase客户端(与之前的情况一样)会立即调用onDataChange()
来获取本地磁盘缓存中的值。它将不再次调用onDataChange()
,即使服务器上的值结果不同也是如此。请注意,仍会请求更新的数据并在后续请求中返回。
以前在How does Firebase sync work, with shared data?
中介绍了这一点最佳解决方案是使用addValueEventListener()
,而不是单值事件侦听器。常规值侦听器将同时获取本地事件和来自服务器的潜在更新。
作为一种解决方法,您还可以call keepSynced(true)
了解使用单值事件侦听器的位置。这可以确保数据在更改时更新,从而大大提高了单值事件侦听器查看当前值的可能性。
答案 1 :(得分:1)
因此,我对此有一个可行的解决方案。您需要做的就是使用ValueEventListener并在1-2秒后删除侦听器,以确保在需要时已获取更新的数据。实时数据库具有很好的延迟,因此很安全。请参见下面的安全代码示例;
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;
}
然后您想要使用“ 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);
}
}, 2000);
}
// 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);
}
}, 2000);
}
即使他们在执行处理程序之前关闭了应用程序,也将始终将其删除。 不客气!
答案 2 :(得分:0)
您可以创建事务并中止它,然后在线(nline数据)或离线(缓存数据)时调用onComplete
我之前创建的函数只有在数据库得到连接lomng足以进行同步时才有效。我通过添加超时修复了问题。我将研究这个并测试它是否有效。也许在将来,当我获得空闲时间时,我将创建android lib并发布它,但到那时它是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)
}
}
在代码中如果设置了超时,则设置定时器,该定时器将使用abort调用事务。即使在离线时也会调用此事务,并且将提供在线或缓存数据(在此函数中,此数据很可能被高速缓存)。
然后我称交易成功。如果我们从firebase数据库得到响应,将仅调用OnComplete
。我们现在可以取消定时器(如果不为空)并将数据发送回回调。
此实现使开发人员99%确定数据来自缓存或在线缓存。
如果你想让它更快脱机(当显然没有连接数据库时不要愚蠢地等待超时),那么在使用上面的函数之前检查数据库是否已连接:
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");
}
});
答案 3 :(得分:0)
当启用了持久性的workinkg时,我计算了监听器接受对onDataChange()的调用并停止监听2次的次数。为我工作,也许有帮助:
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);
}