hellobody
hellobody

Reputation: 530

Why Firebase Realtime Database onChildChanged is triggered when new descendant node is added to the node which being listened?

I have 2 instances of my app running on 2 devices. Both devices are listening to the same DB node changes using onChildChanged method. When new child node is added to the listened node the onChildChanged event only triggered on the same device that did this new node adding. On the second device the onChildChanged event is not triggered for some reason.

But if I use onChildAdded method to be listened then this event onChildAdded is properly triggered on both devices when the node which changes we are listening gets updated by newly added child node.

I'm OK to accept that onChildChanged only triggered when any of descendant nodes actually get changed (not new child node added) on all devices. But why onChildChanged is only triggered on that device which performed new node adding?

So in short the question is: Why when on device A new child node added into DB the onChildAdded triggered on both A and B devices and onChildChanged only triggered on device A (and not on B)?

Related docs: https://firebase.google.com/docs/database/admin/retrieve-data https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener

Update:

I discovered that ServerValue.timestamp causes this behavior. Without ServerValue.timestamp when we add new child node and listening for parent node changes the onChildChanged event has never triggered. Only if we use ServerValue.timestamp as one of the fields of newly added child node the onChildChanged event happens BUT only on the device which added this new child node with server timestamp value. I understand it most likely triggered because first the new child node is created with timestamp field placeholder and then this node is updated by server when real timestamp is written into required field. So now the question is: why not all devices with this application instances get the onChildChanged event in case we use ServerValue.timestamp?

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
          });
        },
      ),
    );
  }
}

Here is test project source code: https://drive.google.com/file/d/1j3eHbD_3UxOhWWmZRfSoKqa-r9dyEwbV/view?usp=sharing

Upvotes: 1

Views: 1071

Answers (1)

Frank van Puffelen
Frank van Puffelen

Reputation: 599876

This code:

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

Will lead to two write events on the client that makes the change:

  1. The local estimate fires a local event immediately when this code executes.
  2. Then when the client receives the actual value from the server, it will fire another event for the final value.

Remote clients listening to this same node, will only see the second event. That can either be a onValue event, or an onChildAdded or onChildChanged event, depending on the type of listener used and the local state.

So on the client making the change: if you use a onValue listener, you'll see two value events, while if you use onChild listeners on the parent node, you'll see an onChildAdded for the local event, and then a onChildChanged for the final value.

Upvotes: 2

Related Questions