web-dev-qa-db-ja.com

Firebaseキャッシュをバイパスしてデータを更新する方法(Androidアプリ)?

Androidアプリケーションは、オンラインの場合に必要なほとんどの時間オフラインで動作する必要があります。つまり、同期操作を行うには:

_User myUser =  MyclientFacade.getUser();
If (myUser.getScore > 10) {
    DoSomething() 
}
_

ユーザーがFirebaseによって満たされたPOJOである場合;

Firebaseキャッシュがアクティブになっているときに問題が発生します

_Firebase.getDefaultConfig().setPersistenceEnabled(true);
_

ユーザーは既にキャッシュにあり、データはFirebase DBでサードパーティ(または別のデバイス)によって更新されます。実際、ユーザーを取得するためにFirebaseにクエリを実行するとき、最初にキャッシュからデータを取得し、後でFirebaseサーバーからの最新データで2番目の変更イベントを取得しますが、手遅れです!

同期メソッドMyclientFacade.getUser()を見てみましょう。

_Public User  getUser()  {
  Firebase ref = myFireBaseroot.child("User").child(uid);
  ref.keepSynced(true);
  /* try {
    Thread.sleep(3000);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }*/
final CountDownLatch signal = new CountDownLatch(1);

ref.addListenerForSingleValueEvent(new ValueEventListener() {
//ref.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
       this.user = dataSnapshot.getValue(User.class);
       signal.countDown();
    }
    @Override
    public void onCancelled(FirebaseError firebaseError) {
       signal.countDown();
    }
});
signal.await(5, TimeUnit.SECONDS);
ref.keepSynced(false);
return this.user;
}
_

addValueEventListenerまたはaddListenerForSingleValueEventを_ref.keepSynced_と組み合わせて使用​​すると、同じ動作になります。

キャッシュ内のユーザーのスコア値が5で、Firebase DBからは11だとします。

getUserを呼び出すと、スコア5を取得します(Firebaseはキャッシュを最初に要求します)。したがって、doSomething()メソッドを呼び出しません。

例からThread.sleep()コードのコメントを外すと、Firebaseキャッシュに更新するのに十分な時間があり、getUserは正しいスコア値を返します(11)。

つまり、サーバー側から直接最新の値を直接問い合わせてキャッシュをバイパスするにはどうすればよいですか?

34
ThierryC

これは私のアプリケーションでも多くのストレスを引き起こしていた問題でした。

.addListenerForSingleValueEvent().addValueEventListener()に変更することから、.keepSynced()を創造的に使用しようとすることから、遅延を使用する(Thread.sleep()メソッド上記)、実際に一貫して機能するものは何もありませんでした(実稼働アプリで実際に受け入れられなかったThread.sleep()メソッドでさえ、一貫した結果をもたらしませんでした)。

だから私はこれでした:Queryオブジェクトを作成し、その上で.keepSynced()を呼び出した後、クエリしているノードでモック/トークンオブジェクトを記述し、THENその操作の完了リスナーで、モックオブジェクトを削除した後、やりたいデータを取得します。

何かのようなもの:

 MockObject mock = new MockObject();
    mock.setIdentifier("delete!");

    final Query query = firebase.child("node1").child("node2");

    query.keepSynced(true);

    firebase.child("node1").child("node2").child("refreshMock")
            .setValue(mock, new CompletionListener() {

                @Override
                public void onComplete(FirebaseError error, Firebase afb) {

                    query.addListenerForSingleValueEvent(new ValueEventListener() {

                        public void onDataChange(DataSnapshot data) {

                            // get identifier and recognise that this data
                            // shouldn't be used
                            // it's also probably a good idea to remove the
                            // mock object
                            // using the removeValue() method in its
                            // speficic node so
                            // that the database won't be saddled with a new
                            // one in every
                            // read operation

                        }

                        public void onCancelled(FirebaseError error) {
                        }

                    });

                }

            });
}

これは今まで一貫して機能していました! (まあ、1日かそこらの間、塩の粒でこれを取る)。何らかの方法で読み取りを行う前に書き込み操作を行ってキャッシュをバイパスするように思えますが、これは理にかなっています。したがって、データは新鮮に戻ります。

唯一の欠点は、読み取り操作の前に余分な書き込み操作があることです。これにより、わずかな遅延が発生する可能性があります(明らかに小さなオブジェクトを使用します)が、それが常に最新のデータの価格であれば、それを取ります!

お役に立てれば!

33
Antonis427

私が発見した回避策は、FirebaseのrunTransaction()メソッドを使用することです。これは、常にサーバーからデータを取得するようです。

String firebaseUrl = "/some/user/datapath/";
final Firebase firebaseClient = new Firebase(firebaseUrl);

    // Use runTransaction to bypass cached DataSnapshot
    firebaseClient.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            // Return passed in data
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(FirebaseError firebaseError, boolean success, DataSnapshot dataSnapshot) {
            if (firebaseError != null || !success || dataSnapshot == null) {
              System.out.println("Failed to get DataSnapshot");
            } else {
              System.out.println("Successfully get DataSnapshot");
              //handle data here
            }
        }
    });
14
Robert

アプリケーションクラスonCreateメソッドにこのコードを追加するだけです。 (データベース参照を変更する)

例:

public class MyApplication extendes Application{

 @Override
    public void onCreate() {
        super.onCreate();
            DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
            scoresRef.keepSynced(true);
      }
}

私にはうまくいきます。

リファレンス: https://firebase.google.com/docs/database/Android/offline-capabilities

2
Cícero Moura

私の解決策は、ロード時にDatabase.database()。isPersistenceEnabled = trueを呼び出し、更新が必要なノードで.keepSynced(true)を呼び出すことでした。

ここでの問題は、.keepSynced(true)の直後にノードを照会すると、新しいデータではなくキャッシュを取得する可能性が高いことです。わずかに不十分ですが機能的な回避策:Firebaseに新しいデータを取得する時間を与えるために、ノードのクエリを1秒ほど遅らせます。ユーザーがオフラインの場合は、代わりにキャッシュを取得します。

ああ、そしてもしそれが永久にバックグラウンドで最新になりたくないノードなら、終わったら.keepSynced(false)を呼び出すことを忘れないでください。

2
Julien Sanders

受け入れられたソリューションとトランザクションの両方を試しました。トランザクションソリューションはよりクリーンで優れていますが、成功OnCompleteはdbがオンラインの場合にのみ呼び出されるため、キャッシュからロードできません。ただし、トランザクションを中止することができ、オフライン(キャッシュされたデータを使用)で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)
                removeListener()
            }

            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)
                removeListener()
            }

            override fun doTransaction(p0: MutableData?): Transaction.Result {
                return Transaction.success(p0)
            }
        }

(明らかにデータベースが接続されていないときにタイムアウトで愚かに待機しないために)オフラインで高速化する場合は、上記の機能を使用する前にデータベースが接続されているかどうかを確認します。

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");
  }
});
2
Janusz Hain