提问者:小点点

Firebase脱机功能和addListenerForSingleValueEvent


每当我将AddListenerForSingLevAlueEventSetPersistenceEnabled(true)一起使用时,我只能设法从服务器获取DataSnapShot的本地脱机副本,而无法获取更新的DataSnapShot

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

这对于AddListenerForSingLevAlueEvent是否正常,因为它只在本地(脱机)搜索DataSnapShot,并在成功检索DataSnapShot一次(脱机或联机)后删除其侦听器?


共3个答案

匿名用户

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()。 然后,If还将启动与服务器的检查,以请求对值的任何更新。 如果自上次将数据添加到缓存后服务器上的数据发生了更改,则随后可能再次调用onDataChange()

将单个值事件侦听器添加到相同位置时:

ref.addListenerForSingleValueEvent(listener);

Firebase客户端将(与前面的情况类似)立即从本地磁盘缓存中调用onDataChange()获取值。 它将不再调用onDataChange(),即使服务器上的值不同。 请注意,更新后的数据仍将被请求,并在后续请求时返回。

这在前面的Firebase sync如何与共享数据一起工作中已经介绍过了?

最好的解决方案是使用AddValueEventListener(),而不是单值事件侦听器。 常规值侦听器将从服务器获取即时本地事件和潜在更新。

作为一种解决方法,您还可以在使用单值事件侦听器的位置调用keepsynced(true)。 这样可以确保每当数据发生变化时都会进行更新,这大大提高了单值事件侦听器看到当前值的机会。

匿名用户

所以我有一个可行的解决方案。 您所要做的就是使用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);
}

即使他们在执行处理程序之前关闭了应用程序,它也会被删除。 不用客气!

匿名用户

您可以创建事务并中止它,然后在联机(n行数据)或脱机(缓存数据)时调用onComplete

我以前创建了一个函数,它只在数据库获得足够的连接来进行同步时才起作用。 我通过添加超时修正了问题。 我会在这方面工作,并测试这是否有效。 也许在未来,当我有空闲时间的时候,我会创建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的事务。 即使在脱机时也会调用此事务,并将提供联机或缓存数据(在此函数中,此数据是缓存数据的几率很高)。 然后我用成功调用事务。 oncomplete只有在从firebase数据库得到响应时才会被调用。 我们现在可以取消计时器(如果不是null)并向回调发送数据。

这个实现使dev 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");
  }
});