【问题标题】:ValueEventListener vs ChildEventListener for RecyclerView in AndroidAndroid 中 RecyclerView 的 ValueEventListener 与 ChildEventListener
【发布时间】:2017-04-22 14:13:42
【问题描述】:

Firebase 数据库用户知道有两个基本的侦听器用于侦听数据:ValueEventListenerChildEventListener。当我们监听一个对象时它工作得很好,但是当我们监听一些集合时就变得相当困难了。

要指定问题,让我们假设我们有 HackerNews 提要,我们会收听例如Firebase 中的“帖子”对象。

当然,我们的应用程序中有RecyclerView 用于显示帖子,我认为使用 FirebaseUI 是个好主意,但问题是我们希望在更改服务器端或测试时创建更抽象的应用程序。所以我们会使用一些适配器,但这是另一个question

正如我提到的,我们有两个听众,问题是哪个更好

当我们使用ValueEventListener 时,我们将获得整个集合,但如果发生任何更改,例如某个用户更改了帖子的内容,我们将不得不重新加载整个数据,这意味着通过昂贵的网络传输发送更多字节。另一个问题是当我们使用多监听器时,这里是一个例子:

Post 有 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 只向我们发送这一个帖子,这意味着通过网络的数据更少.

我们不喜欢将帖子单独添加到RecyclerView,因为例如: - 添加加载指示器很难,因为我们不知道所有数据何时到来。 - 用户获得不断刷新的视图,而新帖子出现而不是整个列表变得可见。 - 很难对集合进行排序,我们可能不得不在适配器中进行排序。

问题

将 firebase 与集合一起使用的最佳做法是什么,以及可能比我上面写的更好的解决方案?

编辑

数据方案如下所示:

"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"
    }
}

编辑 2

我犯了一个错误 -> 当某些事情发生变化时,Firebase 没有获取 ValueEventListener 中的全部数据。它只得到“delta”,here 就是证明。

【问题讨论】:

  • 你能告诉我你的数据库架构吗?
  • 我编辑了问题。
  • 没有你提到的那些。 Firebase UI 的 FirebaseRecyclerAdapter 在这种情况下是最好的
  • 正如我在问题中所说的,如果您想编写更抽象的代码,这是一个非常糟糕的主意。
  • @ThirdMartian 如果你说出为什么我肯定会相信广告在我的路上。使用 FirebaseRecyclerAdapter 时添加进度指示器非常容易

标签: java android firebase firebase-realtime-database


【解决方案1】:

这个问题有几个问题(即性能、进度指示器、处理新数据,例如对它们进行排序)。当然,您应该提出一个考虑到您的需求优先级的解决方案。 IMO ValueEventListenerChildEventListener 都有各自的用例:

  1. ChildEventListener 通常是同步对象列表的推荐方式。 documentation on working with lists中甚至提到了这一点:

    使用列表时,您的应用程序应该侦听子事件而不是用于单个对象的值事件。

    这是因为您的客户端仅接收具有特定更新(添加或删除)的更改子节点,而不是每次更新时的整个列表。因此,它允许对列表的更新进行更精细的处理。

  2. ValueEventListener 在您需要在修改子项时处理整个列表时更有用。当您必须对RecyclerView 中的列表进行排序时就是这种情况。通过获取整个列表,对其进行排序并刷新视图的数据集,这样做要容易得多。另一方面,使用ChildEventListener 进行排序会更加困难,因为每次更新事件您只能访问列表中的一个特定子项。

性能的角度来看,即使ValueEventListener 在同步更新时也知道“增量”,我倾向于认为它的效率较低仅在客户端,因为客户端必须对整个列表进行处理,但这仍然比在网络侧效率低下要好得多。

关于持续刷新和进度指示器,最需要注意的是 Firebase 数据库是一个实时数据库,因此实时数据的持续馈送是固有的特征。如果不需要更新事件,您可以简单地使用addListenerForSingleValueEvent 方法仅读取一次数据。如果您想在加载第一个快照时显示进度指示器,也可以使用它:

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

    ...
});

【讨论】:

  • 排序 -> 使用值事件监听器... 成本有什么区别
【解决方案2】:

我遇到了同样的问题(ValueEventListener vs ChildEventListener for RecyclerView in Android)。

我使用的解决方案是这样结合:

假设 dbref 指向我的 帖子 所在的 firebase 数据库位置。

  1. 使用 vel (ValueEventListener) 用 dbref 处的当前数据列表填充 recyclerview。像这样的东西:vel = dbref.addValueEventListener(vel);
  2. dbref 获得当前数据列表后,我从 dbref 中删除侦听器 veldbref.removeEventListener(vel);。 如果您使用dbref.addListenerForSingleValueEvent(vel);,则不需要这样做 在第 1 步中。
  3. 从现在开始编写查询 query 以过滤插入到 dbref 的新帖子。像这样的东西: query = dbref.orderByChild(post.createdat).startAt(System.currentTimeMillis())
  4. cel (ChildEventListener) 附加到 query 以仅接收新帖子:query.addChildEventListener(cel);

【讨论】:

  • 这就是我的想法。但它的第一次调用和删除 valueEventListener 并附加一个 childEventListener 的差距很小。如果在此期间发生了某些变化(狭窄但不为零),那么我认为您会错过这一点。如果有办法同时添加两者,那将非常有用。
【解决方案3】:

在 Firebase 上处理收集数据时,您应该使用:

  • onChildAdded
  • onChildChanged
  • onChildRemoved
  • onChildMoved

您可能不需要所有这些,但我发现通常onChildAddedonChildRemoved 通常是必不可少的。您必须对适配器中更改的数据进行排序和跟踪,并将其传递回每个更新的子节点的 RecyclerView。

相比之下,您可以使用ValueEventListener 并简单地更新整个列表,但是就像您说的那样,这意味着该列表中的每次更新都会导致集合中的所有对象都发送给您,这将花费您的用户数据并在 Firebase 上花费更多带宽。如果您的数据会经常更新,则不建议这样做。

Source

【讨论】:

  • 请看我的编辑2
  • Firebase 将返回您的ValueEventListener 附加位置的完整数据快照,只要更改孩子即可。 Source
  • 是的,但它不会完全下载它,我只是获取整个数据快照,但数据库内部只会发送“delta”
  • 哦,我现在明白你的意思了。我仍然支持使用子侦听器,因为您不需要迭代整个快照,因为如果您有一个很大的列表,这可能会很多。特别是在您的情况下,据我了解,您不希望视图在调用侦听器时不断更新,因此也许您可以保持不断更新的数据集,但仅在用户想要刷新时才刷新视图,
  • @NitishKasturia 您可以使用ListAdapter,一旦您提供了整个快照AsyncDiffUtil 将通过比较两个列表(新旧列表)来处理其余部分)。
【解决方案4】:

我会在这种情况下使用onchildAdded listener,它有几个优点,首先你已经弄清楚假设有100个帖子的网络操作,即使一个被改变你也会得到所有的回调。而在 onChildListener 中,您将获得唯一更改的帖子的回调。所以你可以做的就是将firebase的回调映射到像这样的回收器视图方法::-

onChildAdded --> 您必须将 dataSnapshot 转换为您的类并调用 recyclerView.notifyItemAdded()

onChildRemoved --> recyclerView.notifyItemRemoved(int)

onChildChanged --> recyclerView.notifyItemChanged(int)

onChildMoved -->(你可能不需要这个,它是为了排序/优先级)

【讨论】:

    猜你喜欢
    • 2018-07-10
    • 2017-06-10
    • 2018-04-16
    • 1970-01-01
    • 2016-12-22
    • 2018-08-16
    • 1970-01-01
    • 2017-08-03
    • 1970-01-01
    相关资源
    最近更新 更多