Firebase脱机功能和addListenerForSingleValueEvent

时间:2015-12-28 00:44:11

标签: android firebase firebase-realtime-database

每当我将addListenerForSingleValueEventsetPersistenceEnabled(true)一起使用时,我只能设法获取DataSnapshot更新的DataSnapshot的本地离线副本服务器。

但是,如果我将addValueEventListenersetPersistenceEnabled(true)一起使用,我可以从服务器获取DataSnapshot的最新副本。

这是addListenerForSingleValueEvent的正常现象,因为它仅在本地(离线)搜索DataSnapshot并在成功检索DataSnapshot ONCE 后离线或移除其监听器(离线或在线) )?

4 个答案:

答案 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);
}