#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
});
Приведет к двум событиям записи на клиенте, который вносит изменения:
- Локальная оценка немедленно запускает локальное событие при выполнении этого кода.
- Затем, когда клиент получает фактическое значение с сервера, он запускает другое событие для конечного значения.
Удаленные клиенты, прослушивающие этот же узел, увидят только второе событие. Это может быть либо 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
. Если я правильно понимаю сейчас, и вы согласны, тогда мы должны это исправить «…увидит только второе событие «.