web-dev-qa-db-ja.com

AndroidのRecyclerViewのValueEventListenerとChildEventListener

Firebase Databaseのユーザーは、データをリッスンするための2つの基本的なリスナーがValueEventListenerChildEventListenerであることを知っています。 1つのオブジェクトをリッスンするとうまく機能しますが、コレクションをリッスンすると非常に困難になります。

質問を指定するために、HackerNewsフィードがあることを想像してみましょう。 Firebaseの「投稿」オブジェクト。

もちろん、投稿を表示するアプリにはRecyclerViewがあり、FirebaseUIを使用することをお勧めしますが、問題はサーバー側またはテストを変更する場合により抽象的なアプリケーションを作成することです。したがって、いくつかのアダプターを使用しますが、これは別の question です。

先ほど述べたように、リスナーは2人います。質問はの方が良いです

ValueEventListenerを使用すると、コレクション全体が取得されますが、1人のユーザーが投稿のコンテンツを変更した場合など、変更があった場合は、データ全体を再ロードする必要があります。もう1つの問題は、マルチリスナーを使用する場合です。以下に例を示します。

投稿にはuserIdがありますが、彼の名前を表示したいので、onDataChangedメソッドでは、次のようにユーザーデータを取得します。

postsReference.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot data : dataSnapshot.getChildren()) {
            Post post = data.getValue(Post.class);   
            usersReference.child(post.getUserId()).addListenerForSingleValueEvent(new ValueEventListener() {

                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    // Here we have user data
                }

                @Override
                public void onCancelled(FirebaseError firebaseError) {

                }
            });
        }
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
    }
});

ここで、各投稿をRecyclerViewに個別に追加する必要があることがわかります。これにより、ChildEventListenerを使用する必要があると思われます。

したがって、「ChildEventListener」を使用する場合、問題は同じです。各投稿をRecyclerViewに個別に追加する必要がありますが、誰かが投稿のコンテンツを変更すると、firebaseはこの1つの投稿のみを送信するため、ネットワーク経由のデータが少なくなります。

ポストを個別にRecyclerViewに追加するのは好きではありません。なぜなら、たとえば:-ロードインジケーターを追加するのは難しいため、すべてのデータがいつ来るかわからないからです。 -ユーザーは絶えずビューを更新しますが、リスト全体ではなく新しい投稿が表示されます。 -そのコレクションを並べ替えるのは難しいです。おそらくアダプタで行う必要があります。

質問

コレクションでfirebaseを使用するためのベストプラクティスは何ですか?また、上記で書いたよりも優れたソリューションでしょうか

[〜#〜] edit [〜#〜]

データスキームは次のようになります。

"posts" : {
    "123456" : {
        "createdAt" : 1478696885622,
        "content" : "This is post content",
        "title" : "This is post title",
        "userId" : "abc"
    },
    "789012" : {
        "createdAt" : 1478696885622,
        "content" : "This is post content 2",
        "title" : "This is post title 2",
        "userId" : "efg"
    }
}
"users" : {
    "abc" : {
        "name" : "username1"
    },
    "efg" : {
        "name" : "username2"
    }
}

EDIT 2

間違えた-> Firebaseは、何かが変更されたときにValueEventListenerのデータ全体を取得していません。 「デルタ」のみを取得します。 ここ は証明です。

15
Nominalista

この質問にはいくつかの懸念事項があります(つまり、パフォーマンス、進行状況インジケーター、並べ替えなどの新しいデータの処理)。当然、要件の優先度を考慮したソリューションを考え出す必要があります。 IMO ValueEventListenerChildEventListenerの両方にユースケースがあります:

  1. ChildEventListenerは一般に、オブジェクトのリストを同期する推奨方法です。これは リストの操作に関するドキュメント でも言及されています:

    リストで作業する場合、アプリケーションは、単一オブジェクトに使用される値イベントではなく、子イベントをリッスンする必要があります。

    これは、すべての更新のリスト全体ではなく、クライアントが特定の更新(追加または削除)で変更された子のみを受け取るためです。したがって、リストの更新をより詳細なレベルで処理できます。

  2. ValueEventListenerは、子の変更時にリスト全体を処理する必要がある場合により便利です。これは、RecyclerViewのリストを並べ替える必要がある場合です。リスト全体を取得してソートし、ビューのデータセットを更新することで、これを行うのがはるかに簡単になります。一方、ChildEventListenerを使用すると、更新イベントごとにリスト内の特定の1つの子にしかアクセスできないため、並べ替えがより困難になります。

performanceの観点から、ValueEventListenerでさえもアップデートの同期中に「デルタ」を認識しているとすれば、クライアントはリスト全体に対して処理を行う必要があるため、効率が低いと考える傾向がありますクライアント側のみ。これは、ネットワーク側で非効率性を保持するよりもはるかに優れています。

一定の更新と進捗インジケータに関して、最も重要なことは、Firebaseデータベースがリアルタイムデータベースであるため、リアルタイムデータの常時供給は固有の特性です。更新イベントが不要な場合は、addListenerForSingleValueEventメソッドを使用してデータを1回だけ読み取ることができます。最初のスナップショットのロード時に進行状況インジケーターを表示する場合にも、これを使用できます。

// show progress indicator
postsReference.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // load initial data set
        // hide progress indicator when done loading
    }

    ...
});
19
manouti

私はまったく同じ問題を抱えていました(AndroidのValueEventListenerChildEventListenerRecyclerView)。

私が使用した解決策は、この両方を組み合わせる方法でした:

dbrefが、私のpostsがあるfirebase dbの場所を指しているとします。

  1. vel(ValueEventListener)を使用して、recyclerviewdbrefの現在のデータのリストを入力します。このようなもの:vel = dbref.addValueEventListener(vel);
  2. dbrefで現在のデータのリストを取得したら、リスナーveldbrefから削除します:dbref.removeEventListener(vel);。手順1でdbref.addListenerForSingleValueEvent(vel);を使用した場合、これは必要ありません。
  3. クエリを書くqueryこれからdbrefに挿入された新しい投稿をフィルタリングします。このようなもの:query = dbref.orderByChild(post.createdat).startAt(System.currentTimeMillis())
  4. cel(ChildEventListener)をqueryに添付して、新しい投稿のみを受信します:query.addChildEventListener(cel);
4
Jean Menye

Firebaseでコレクションデータを操作するときは、次を使用する必要があります。

  • onChildAdded
  • onChildChanged
  • onChildRemoved
  • onChildMoved

これらのすべてが必要なわけではないかもしれませんが、通常onChildAddedonChildRemovedがしばしば不可欠であることがわかりました。アダプターで変更されるデータを並べ替えて追跡し、更新されたすべての子でごみ箱にそれを渡す必要があります。

対照的に、ValueEventListenerを使用してリスト全体を更新することもできますが、前述のように、リスト内のすべての更新によりコレクション内のすべてのオブジェクトが送信され、ユーザーにコストがかかりますFirebaseでより多くの帯域幅を必要とします。データが頻繁に更新される場合、これはお勧めしません。

ソース

2
Nitish Kasturia

この場合はいつでもonchildAddedリスナーを使用しますが、いくつかの利点があります。まず、100件の投稿があると仮定した場合のネットワーク操作を把握し、変更された場合でも、それらすべてのコールバックを取得します。一方、onChildListenerでは、変更された唯一の投稿のコールバックを取得します。そのため、次のようなリサイクラビューメソッドを使用して、firebaseのコールバックをマップすることができます。

onChildAdded-> dataSnapshotをクラスにキャストし、recyclerView.notifyItemAdded()を呼び出す必要があります

onChildRemoved-> recyclerView.notifyItemRemoved(int)

onChildChanged-> recyclerView.notifyItemChanged(int)

onChildMoved->(おそらくこれは必要ないでしょう、それは注文/優先順位のためです)

2