web-dev-qa-db-ja.com

Firebaseオフライン機能とaddListenerForSingleValueEvent

setPersistenceEnabled(true)addListenerForSingleValueEventを使用するときはいつでも、DataSnapshotおよび[〜#〜] not [〜#〜]のローカルオフラインコピーのみを取得できます。サーバーから更新されたDataSnapshot

ただし、setPersistenceEnabled(true)addValueEventListenerを使用すると、サーバーからDataSnapshotの最新のコピーを取得できます。

これはaddListenerForSingleValueEventのローカル(オフライン)のみを検索し、DataSnapshotを正常に取得した後にリスナーを削除するため、DataSnapshotの通常の動作です。[〜#〜] once [〜# 〜](オフラインまたはオンライン)?

52
Jason Hoch

永続性の仕組み

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()を呼び出します。その後、値の更新を要求するために、サーバーとのチェックも開始します。 It may最後にキャッシュに追加されてからサーバー上のデータに変更があった場合、その後onDataChange()を再度呼び出します。

addListenerForSingleValueEventを使用するとどうなりますか

単一値のイベントリスナーを同じ場所に追加する場合:

ref.addListenerForSingleValueEvent(listener);

Firebaseクライアントは、(前の状況と同様に)ローカルディスクキャッシュの値に対してすぐにonDataChange()を呼び出します。サーバー上の値が異なることが判明した場合でも、notonDataChange()をさらに呼び出します。更新されたデータは引き続き要求され、後続の要求で返されることに注意してください。

これについては、以前に Firebaseの同期はどのように機能しますか、共有データを使用しますか

解決策と回避策

最善の解決策は、単一値のイベントリスナーの代わりに addValueEventListener() を使用することです。通常の値のリスナーは、即時のローカルイベントとサーバーからの潜在的な更新の両方を取得します。

回避策として、単一値イベントリスナを使用する場所で call keepSynced(true) を実行することもできます。これにより、データが変更されるたびに確実に更新されるため、単一値のイベントリスナーが現在の値を見る可能性が大幅に向上します。

70

トランザクションを作成して中止すると、onCompleteがオンライン(nlineデータ)またはオフライン(キャッシュされたデータ)のときに呼び出されます

以前は、データベースが同期を実行するのに十分な接続を取得した場合にのみ機能する関数を作成しました。タイムアウトを追加して問題を修正しました。これに取り組み、これが機能するかどうかをテストします。将来的には、空き時間があるときに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)
            }
        }

コード内でタイムアウトが設定されている場合、アボートでトランザクションを呼び出すタイマーを設定します。このトランザクションはオフラインでも呼び出され、オンラインまたはキャッシュされたデータを提供します(この機能では、このデータがキャッシュされている可能性が非常に高いです)。次に、トランザクションを正常に呼び出します。 OnCompleteは、firebaseデータベースから応答があった場合にのみ呼び出されます。タイマー(nullでない場合)をキャンセルして、コールバックにデータを送信できるようになりました。

この実装により、開発者はデータがキャッシュからのものであるかオンラインのものであることを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");
  }
});
0
Janusz Hain

永続性を有効にして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);
}
0
german