【问题标题】:Why Firebase Realtime Database onChildChanged is triggered when new descendant node is added to the node which being listened?为什么将新的后代节点添加到正在侦听的节点时会触发 Firebase 实时数据库 onChildChanged?
【发布时间】:2021-02-04 11:36:41
【问题描述】:

我的应用有 2 个实例在 2 台设备上运行。 两台设备都使用onChildChanged 方法监听相同的数据库节点更改。 当新的子节点添加到监听的节点时,onChildChanged 事件仅在添加此新节点的同一设备上触发。 在第二台设备上,onChildChanged 事件由于某种原因没有被触发。

但是如果我使用onChildAdded 方法被监听,那么当我们正在监听的节点被新添加的子节点更新时,这个事件onChildAdded 会在两个设备上正确触发。

我可以接受 onChildChanged 仅在所有设备上实际更改任何后代节点(未添加新子节点)时触发。但是为什么onChildChanged只在执行新节点添加的设备上触发?

所以简而言之,问题是: 为什么在设备 A 上将新的子节点添加到 DB 时,onChildAdded 在 A 和 B 设备上触发,而onChildChanged 仅在设备 A(而不是 B)上触发?

相关文档: https://firebase.google.com/docs/database/admin/retrieve-data https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener

更新:

我发现 ServerValue.timestamp 导致了这种行为。 当我们添加新的子节点并监听父节点更改时,如果没有 ServerValue.timestamp,onChildChanged 事件永远不会触发。 仅当我们使用 ServerValue.timestamp 作为新添加的子节点的字段之一时,onChildChanged 事件才会发生但仅在添加了具有服务器时间戳值的新子节点的设备上。 我理解它很可能被触发,因为首先使用时间戳字段占位符创建新的子节点,然后在将实际时间戳写入必填字段时由服务器更新该节点。 所以现在的问题是:如果我们使用 ServerValue.timestamp,为什么不是所有具有此应用程序实例的设备都获得 onChildChanged 事件?

class _MyHomePageState extends State<MyHomePage> {
  
  final String dbDataPath = 'root/chatroom0001/messages';
  DatabaseReference dbRef = FirebaseDatabase.instance.reference ();
  StreamSubscription <Event> _messagesChangedSubscription;
  List <String> _lMessages = List <String> ();

  Future <List <String>> fetchMessages (String dbDataPath) async {
    List <String> lMessages = List <String> ();
    final DataSnapshot dataMessages = await dbRef.child (dbDataPath).once ();

    dataMessages.value.forEach ((k, v) => {
      lMessages.add (v ['message'])
    });

    return lMessages;
  }

  @override
  void initState () {
    super.initState ();

    DatabaseReference messagesRef = dbRef.child (dbDataPath);

    fetchMessages (dbDataPath).then ((lMessages) {
      _lMessages = lMessages;
      setState (() {});
    });

    _messagesChangedSubscription = dbRef.child (dbDataPath).onChildChanged.listen ((event) {
      fetchMessages (dbDataPath).then ((lMessages) {
        _lMessages = lMessages;
        setState (() {});
      });
    });
  }

  @override
  void dispose () {
    super.dispose ();

    _messagesChangedSubscription.cancel ();
  }

  @override
  Widget build (BuildContext context) {
    return Scaffold (      
      body: Container (
        color: Colors.red, child: ListView.builder (
          itemCount: _lMessages.length,
          itemBuilder: (context, i) {
            return Container (padding: EdgeInsets.all (5), child: Text (_lMessages [i], style: TextStyle (fontWeight: FontWeight.bold, fontSize: 20.0)));
          }
        )
      ),
      floatingActionButton: FloatingActionButton (
        onPressed: () {
          DatabaseReference messagesRef = dbRef.child (dbDataPath);
          DatabaseReference newChatMessageRef = messagesRef.push ();
          newChatMessageRef.set ({
            'message': 'Hello Google!',
            'date': ServerValue.timestamp
          });
        },
      ),
    );
  }
}

这里是测试项目源代码: https://drive.google.com/file/d/1j3eHbD_3UxOhWWmZRfSoKqa-r9dyEwbV/view?usp=sharing

【问题讨论】:

  • onChildChanged 事件针对已更改的子节点触发,根据我的经验,它的行为始终如预期。如果您似乎没有遇到这种情况,请编辑您的问题以显示我们任何人都可以重现该问题的最少代码。
  • @FrankvanPuffelen 我发现这是由 ServerValue.timestamp 引起的。请观看我在其中解释问题的简短视频:drive.google.com/file/d/1F9pkrm2OpWqLAg1eTI-Xjkynztb2SwtF/… 我还使用新信息更新问题描述。
  • @FrankvanPuffelen 请使用此链接下载重现所描述情况的项目的源代码:drive.google.com/file/d/1j3eHbD_3UxOhWWmZRfSoKqa-r9dyEwbV/… 感谢您的帮助。
  • 对不起@hellobody,但在 Stack Overflow 上,需要包含在问题本身中重现问题的最少代码。与外部资源的链接往往会过时,通常会导致数据过多。既然您已经发现了原因,您可以编辑您的问题以包含显示问题的代码吗?请注意,与问题的书面描述相比,代码和日志输出更有可能得到您的答案。
  • @FrankvanPuffelen 刚刚添加了源代码。请你现在看看。谢谢。

标签: firebase flutter dart firebase-realtime-database


【解决方案1】:

这段代码:

  DatabaseReference newChatMessageRef = messagesRef.push ();
  newChatMessageRef.set ({
    'message': 'Hello Google!',
    'date': ServerValue.timestamp
  });

将导致客户端上进行更改的两个写入事件:

  1. 执行此代码时,本地估计会立即触发本地事件。
  2. 然后当客户端从服务器接收到实际值时,它会为最终值触发另一个事件。

监听同一节点的远程客户端只会看到第二个事件。这可以是onValue 事件,也可以是onChildAddedonChildChanged 事件,具体取决于使用的侦听器类型和本地状态。

所以在客户端进行更改:如果您使用 onValue 侦听器,您将看到两个值事件,而如果您在父节点上使用 onChild 侦听器,您将看到 onChildAdded for本地事件,然后是 onChildChanged 作为最终值。

【讨论】:

  • 感谢您的回答,但我有一些问题: 1. 什么是“本地估算”? 2. 你写道“远程客户端监听同一个节点,只会看到第二个事件。”后来你说 onChildChanged 是第二个事件。但是我的测试用例中的远程客户端没有得到 onChildChanged。
  • 1) 客户端根据本地时间及其在本地客户端和服务器之间的最后已知偏移量估计服务器上的时间戳时间戳大致是什么,2) 正确,@987654329 @ 和 onChildChanged 是节点上数据与其先前已知值的本地解释。因此,对于远程客户端,如果客户端之前从未见过该键的子值,那么在您的场景中,他们获得的值(来自第 2 步)将是 onChildAdded
  • 很好的解释。谢谢。唯一令人困惑的是,您调用了 onChildChanged - first 事件和 onChildAdded - second 事件。什么似乎不是真的。刚刚在调试下测试:实际上首先发生onChildAdded 然后onChildChanged。所以远程客户端只能看到 FIRST 事件onChildAdded 已经应用了来自服务器的最终时间戳。只有在onChildAdded 之后实际发起数据更改的客户端也会得到onChildChanged。如果我现在的理解是正确的并且您同意,那么我们必须解决这个“......只会看到第二个事件”。
猜你喜欢
  • 2020-03-05
  • 1970-01-01
  • 2019-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
  • 2016-12-08
相关资源
最近更新 更多