Почему Firebase Realtime Database onChildChanged запускается при добавлении нового дочернего узла к прослушиваемому узлу?

#firebase #flutter #dart #firebase-realtime-database

#firebase #flutter #dart #firebase-realtime-database

Вопрос:

У меня есть 2 экземпляра моего приложения, запущенных на 2 устройствах. Оба устройства прослушивают одни и те же изменения узла БД с использованием onChildChanged метода. Когда новый дочерний узел добавляется к прослушиваемому узлу onChildChanged , событие запускается только на том же устройстве, на котором был добавлен этот новый узел. На втором устройстве onChildChanged событие по какой-то причине не запускается.

Но если я использую onChildAdded метод для прослушивания, то это событие onChildAdded правильно запускается на обоих устройствах, когда узел, который изменяется, который мы прослушиваем, обновляется вновь добавленным дочерним узлом.

Я согласен с тем, что onChildChanged это срабатывает только тогда, когда какой-либо из узлов-потомков фактически изменяется (не добавляется новый дочерний узел) на всех устройствах. Но почему onChildChanged запускается только на том устройстве, которое выполнило добавление нового узла?

Короче говоря, возникает вопрос: почему, когда на устройстве новый дочерний узел добавлен в БД 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 происходит, НО только на устройстве, которое добавило этот новый дочерний узел со значением метки времени сервера. Я понимаю, что это, скорее всего, срабатывает, потому что сначала создается новый дочерний узел с заполнителем поля метки времени, а затем этот узел обновляется сервером, когда в обязательное поле записывается метка реального времени. Итак, теперь вопрос: почему не все устройства с экземплярами этого приложения получают событие onChildChanged в случае, если мы используем 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
          });
        },
      ),
    );
  }
}
  

Вот исходный код тестового проекта:
https://drive.google.com/file/d/1j3eHbD_3UxOhWWmZRfSoKqa-r9dyEwbV/view?usp=sharing

Комментарии:

1. onChildChanged Событие срабатывает для дочернего узла, который изменен, и, по моему опыту, всегда вел себя так, как ожидалось. Если у вас этого не происходит, отредактируйте свой вопрос, чтобы показать минимальный код, с помощью которого любой из нас может это воспроизвести.

2. @FrankvanPuffelen я обнаружил, что это вызвано ServerValue.timestamp . Пожалуйста, посмотрите это короткое видео, в котором я объясняю проблему: drive.google.com/file/d/1F9pkrm2OpWqLAg1eTI-Xjkynztb2SwtF/… Я также обновляю описание вопроса новой информацией.

3. @FrankvanPuffelen пожалуйста, воспользуйтесь этой ссылкой, чтобы загрузить исходный код проекта, который воспроизводит описанную ситуацию: drive.google.com/file/d/1j3eHbD_3UxOhWWmZRfSoKqa-r9dyEwbV/… Спасибо за вашу помощь.

4. Извините @hellobody, но при переполнении стека требуется включить минимальный код, который воспроизводит проблему в самом вопросе. Ссылки на внешние ресурсы, как правило, устаревают и обычно приводят к слишком большому количеству данных. Поскольку вы обнаружили причину, можете ли вы отредактировать свой вопрос, чтобы включить код, который показывает проблему? Обратите внимание, что код и выходные данные журнала с гораздо большей вероятностью получат ваш ответ, чем письменное описание проблемы.

5. @FrankvanPuffelen только что добавил исходный код. Не могли бы вы, пожалуйста, взглянуть сейчас. Спасибо.

Ответ №1:

Этот код:

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

Приведет к двум событиям записи на клиенте, который вносит изменения:

  1. Локальная оценка немедленно запускает локальное событие при выполнении этого кода.
  2. Затем, когда клиент получает фактическое значение с сервера, он запускает другое событие для конечного значения.

Удаленные клиенты, прослушивающие этот же узел, увидят только второе событие. Это может быть либо onValue событие, onChildAdded либо onChildChanged событие или, в зависимости от типа используемого прослушивателя и локального состояния.

Итак, на клиенте, вносящем изменения: если вы используете onValue прослушиватель, вы увидите два события значений, в то время как если вы используете onChild прослушиватели на родительском узле, вы увидите onChildAdded для локального события, а затем onChildChanged для конечного значения.

Комментарии:

1. Спасибо за ответ, но у меня есть несколько вопросов: 1. Что такое «локальная оценка»? 2. Вы написали, что «Удаленные клиенты, прослушивающие этот же узел, увидят только второе событие». И позже вы сказали, что onChildChanged — это второе событие. Но удаленные клиенты в моем тестовом примере не получают onChildChanged.

2. 1) Клиент оценивает, какая временная метка времени примерно будет на сервере, на основе местного времени и его последнего известного смещения между локальным клиентом и сервером, 2) Правильно, onChildAdded и onChildChanged являются локальными интерпретациями данных на узле по сравнению с его ранее известным значением. Итак, для удаленных клиентов значение, которое они получают (из шага # 2), будет onChildAdded в вашем сценарии, если клиент никогда раньше не видел дочернее значение для этого ключа.

3.Хорошее объяснение. Спасибо. Единственное, что сбивает с толку, это то, что вы вызываете onChildChanged первое событие и onChildAdded второе событие. Что, похоже, не соответствует действительности. Только что протестировано в режиме отладки: на самом деле сначала происходит onChildAdded , а затем onChildChanged . Таким образом, удаленные клиенты видят только ПЕРВОЕ событие onChildAdded с уже примененной окончательной меткой времени с сервера. И только клиент, который фактически инициировал изменение данных после onChildAdded , также получает onChildChanged . Если я правильно понимаю сейчас, и вы согласны, тогда мы должны это исправить «…увидит только второе событие «.