В этом документе рассматривается работа со списками данных в Firebase. Основы чтения и записи данных в Firebase см. в разделе «Чтение и запись данных в Android» .
Получить ссылку на базу данных
Для чтения и записи данных из базы данных необходим экземпляр класса DatabaseReference :
Kotlin
private lateinit var database: DatabaseReference // ... database = Firebase.database.reference
Java
private DatabaseReference mDatabase; // ... mDatabase = FirebaseDatabase.getInstance().getReference();
Чтение и запись списков
Добавить к списку данных
В многопользовательских приложениях метод push() используется для добавления данных в список. Метод push() генерирует уникальный ключ каждый раз, когда к указанной ссылке Firebase добавляется новый дочерний элемент. Используя эти автоматически генерируемые ключи для каждого нового элемента в списке, несколько клиентов могут одновременно добавлять дочерние элементы в одно и то же место без конфликтов записи. Уникальный ключ, генерируемый методом push() , основан на метке времени, поэтому элементы списка автоматически упорядочиваются в хронологическом порядке.
Вы можете использовать ссылку на новые данные, возвращаемые методом push() , чтобы получить значение автоматически сгенерированного ключа дочернего элемента или установить данные для дочернего элемента. Вызов метода getKey() для ссылки на метод push() возвращает значение автоматически сгенерированного ключа.
Вы можете использовать эти автоматически сгенерированные ключи для упрощения преобразования структуры данных в плоскую. Для получения дополнительной информации см. пример разветвления данных.
Слушайте новости о детских мероприятиях
При работе со списками ваше приложение должно отслеживать события, относящиеся к дочерним элементам, а не к значениям, используемым для отдельных объектов.
События, связанные с дочерними узлами, запускаются в ответ на определенные операции, происходящие с дочерними узлами, например, добавление нового дочернего узла с помощью метода push() или обновление дочернего узла с помощью метода updateChildren() . В совокупности все эти события могут быть полезны для отслеживания изменений в конкретном узле базы данных.
Для отслеживания событий дочерних элементов объекта DatabaseReference , подключите объект ChildEventListener :
| Слушатель | Обратный вызов события | Типичное использование |
|---|---|---|
ChildEventListener | onChildAdded() | Получайте списки элементов или отслеживайте добавления элементов в список. Этот обратный вызов срабатывает один раз для каждого существующего дочернего элемента, а затем снова каждый раз, когда к указанному пути добавляется новый дочерний элемент. DataSnapshot , передаваемый слушателю, содержит данные нового дочернего элемента. |
onChildChanged() | Отслеживайте изменения элементов в списке. Это событие срабатывает всякий раз, когда изменяется дочерний узел, включая любые изменения потомков дочернего узла. DataSnapshot , передаваемый обработчику событий, содержит обновленные данные для дочернего узла. | |
onChildRemoved() | Отслеживайте удаление элементов из списка. DataSnapshot , передаваемый в функцию обратного вызова события, содержит данные для удаленного дочернего элемента. | |
onChildMoved() | Отслеживайте изменения порядка элементов в упорядоченном списке. Это событие срабатывает всякий раз, когда вызывается функция обратного вызова onChildChanged() при обновлении, которое приводит к изменению порядка дочернего элемента. Оно используется с данными, упорядоченными с помощью orderByChild или orderByValue . |
Например, приложение для ведения социальных блогов может использовать эти методы вместе для отслеживания активности в комментариях к публикации, как показано ниже:
Kotlin
val childEventListener = object : ChildEventListener { override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!) // A new comment has been added, add it to the displayed list val comment = dataSnapshot.getValue<Comment>() // ... } override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildChanged: ${dataSnapshot.key}") // A comment has changed, use the key to determine if we are displaying this // comment and if so displayed the changed comment. val newComment = dataSnapshot.getValue<Comment>() val commentKey = dataSnapshot.key // ... } override fun onChildRemoved(dataSnapshot: DataSnapshot) { Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!) // A comment has changed, use the key to determine if we are displaying this // comment and if so remove it. val commentKey = dataSnapshot.key // ... } override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!) // A comment has changed position, use the key to determine if we are // displaying this comment and if so move it. val movedComment = dataSnapshot.getValue<Comment>() val commentKey = dataSnapshot.key // ... } override fun onCancelled(databaseError: DatabaseError) { Log.w(TAG, "postComments:onCancelled", databaseError.toException()) Toast.makeText( context, "Failed to load comments.", Toast.LENGTH_SHORT, ).show() } } databaseReference.addChildEventListener(childEventListener)
Java
ChildEventListener childEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey()); // A new comment has been added, add it to the displayed list Comment comment = dataSnapshot.getValue(Comment.class); // ... } @Override public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey()); // A comment has changed, use the key to determine if we are displaying this // comment and if so displayed the changed comment. Comment newComment = dataSnapshot.getValue(Comment.class); String commentKey = dataSnapshot.getKey(); // ... } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey()); // A comment has changed, use the key to determine if we are displaying this // comment and if so remove it. String commentKey = dataSnapshot.getKey(); // ... } @Override public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey()); // A comment has changed position, use the key to determine if we are // displaying this comment and if so move it. Comment movedComment = dataSnapshot.getValue(Comment.class); String commentKey = dataSnapshot.getKey(); // ... } @Override public void onCancelled(DatabaseError databaseError) { Log.w(TAG, "postComments:onCancelled", databaseError.toException()); Toast.makeText(mContext, "Failed to load comments.", Toast.LENGTH_SHORT).show(); } }; databaseReference.addChildEventListener(childEventListener);
Обращайте внимание на важные события.
Хотя использование ChildEventListener является рекомендуемым способом чтения списков данных, существуют ситуации, когда полезно прикрепить ValueEventListener к ссылке на список.
Прикрепление ValueEventListener к списку данных вернет весь список данных в виде единого DataSnapshot , по которому затем можно пройтись циклом для доступа к отдельным дочерним элементам.
Даже если для запроса найдено только одно совпадение, снимок все равно представляет собой список; он просто содержит один элемент. Чтобы получить доступ к элементу, необходимо пройтись циклом по результату:
Kotlin
// My top posts by number of stars myTopPostsQuery.addValueEventListener(object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { for (postSnapshot in dataSnapshot.children) { // TODO: handle the post } } override fun onCancelled(databaseError: DatabaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()) // ... } })
Java
// My top posts by number of stars myTopPostsQuery.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // ... } });
Этот шаблон может быть полезен, когда вам нужно получить все дочерние элементы списка за одну операцию, вместо того чтобы отслеживать дополнительные события onChildAdded .
Отключить слушателей
Обратные вызовы удаляются путем вызова метода removeEventListener() в вашей ссылке на базу данных Firebase.
Если обработчик событий был добавлен к местоположению данных несколько раз, он вызывается несколько раз для каждого события, и для полного удаления его необходимо отсоединить столько же раз.
Вызов removeEventListener() для родительского обработчика событий не приводит к автоматическому удалению обработчиков, зарегистрированных на его дочерних узлах; removeEventListener() также необходимо вызвать для всех дочерних обработчиков событий, чтобы удалить функцию обратного вызова.
Сортировка и фильтрация данных
С помощью класса Realtime Database Query можно получать данные, отсортированные по ключу, по значению или по значению дочернего элемента. Также можно отфильтровать отсортированный результат, выбрав определенное количество результатов или диапазон ключей или значений.
Сортировка данных
Для получения отсортированных данных сначала укажите один из методов сортировки, чтобы определить, как будут упорядочены результаты:
| Метод | Использование |
|---|---|
orderByChild() | Результаты сортировки определяются по значению указанного дочернего ключа или вложенного дочернего пути. | orderByKey() | Сортировать результаты по дочерним ключам. |
orderByValue() | Сортировать результаты по значениям дочерних элементов. |
Одновременно можно использовать только один метод сортировки. Многократный вызов метода сортировки в одном запросе приведет к ошибке.
Следующий пример демонстрирует, как можно получить список лучших постов пользователя, отсортированных по количеству звезд:
Kotlin
// My top posts by number of stars val myUserId = uid val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId) .orderByChild("starCount") myTopPostsQuery.addChildEventListener(object : ChildEventListener { // TODO: implement the ChildEventListener methods as documented above // ... })
Java
// My top posts by number of stars String myUserId = getUid(); Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId) .orderByChild("starCount"); myTopPostsQuery.addChildEventListener(new ChildEventListener() { // TODO: implement the ChildEventListener methods as documented above // ... });
Это определяет запрос, который в сочетании с дочерним обработчиком синхронизирует клиент с сообщениями пользователя из пути в базе данных на основе его идентификатора пользователя, упорядоченного по количеству звезд, полученных каждым сообщением. Этот метод использования идентификаторов в качестве ключей индекса называется «расширение данных» (data fan out), подробнее об этом можно прочитать в статье «Структурирование базы данных» .
Вызов метода orderByChild() указывает ключ дочернего элемента, по которому следует сортировать результаты. В данном случае записи сортируются по значению соответствующего дочернего элемента "starCount" . Запросы также можно сортировать по вложенным дочерним элементам, если у вас есть данные, которые выглядят следующим образом:
"posts": {
"ts-functions": {
"metrics": {
"views" : 1200000,
"likes" : 251000,
"shares": 1200,
},
"title" : "Why you should use TypeScript for writing Cloud Functions",
"author": "Doug",
},
"android-arch-3": {
"metrics": {
"views" : 900000,
"likes" : 117000,
"shares": 144,
},
"title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
"author": "Doug",
}
}, В этом примере мы можем упорядочить элементы списка по значениям, вложенным под ключом metrics , указав относительный путь к вложенному дочернему элементу в вызове функции orderByChild() .
Kotlin
// Most viewed posts val myMostViewedPostsQuery = databaseReference.child("posts") .orderByChild("metrics/views") myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener { // TODO: implement the ChildEventListener methods as documented above // ... })
Java
// Most viewed posts Query myMostViewedPostsQuery = databaseReference.child("posts") .orderByChild("metrics/views"); myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() { // TODO: implement the ChildEventListener methods as documented above // ... });
Для получения дополнительной информации о порядке упорядочивания других типов данных см. раздел «Как упорядочиваются данные запроса» .
Фильтрация данных
Для фильтрации данных при построении запроса можно комбинировать любой из методов ограничения или диапазона с методом сортировки.
| Метод | Использование |
|---|---|
limitToFirst() | Устанавливает максимальное количество элементов, возвращаемых с начала упорядоченного списка результатов. |
limitToLast() | Устанавливает максимальное количество элементов, возвращаемых с конца упорядоченного списка результатов. |
startAt() | Возвращает элементы, большие или равные указанному ключу или значению, в зависимости от выбранного метода сортировки. |
startAfter() | Возвращает элементы, превышающие указанный ключ или значение, в зависимости от выбранного метода сортировки. |
endAt() | Возвращает элементы, меньшие или равные указанному ключу или значению в зависимости от выбранного метода сортировки. |
endBefore() | Возвращает количество элементов меньше указанного ключа или значения в зависимости от выбранного метода сортировки. |
equalTo() | Возвращает элементы, равные указанному ключу или значению в зависимости от выбранного метода сортировки. |
В отличие от методов сортировки, вы можете комбинировать несколько функций ограничения или диапазона. Например, вы можете объединить методы startAt() и endAt() , чтобы ограничить результаты заданным диапазоном значений.
Даже если для запроса найдено только одно совпадение, снимок все равно представляет собой список; он просто содержит один элемент. Чтобы получить доступ к элементу, необходимо пройтись циклом по результату:
Kotlin
// My top posts by number of stars myTopPostsQuery.addValueEventListener(object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { for (postSnapshot in dataSnapshot.children) { // TODO: handle the post } } override fun onCancelled(databaseError: DatabaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()) // ... } })
Java
// My top posts by number of stars myTopPostsQuery.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // ... } });
Ограничьте количество результатов
Методы limitToFirst() и limitToLast() позволяют установить максимальное количество дочерних элементов, синхронизируемых для данного коллбэка. Например, если вы используете limitToFirst() для установки лимита в 100, вы изначально получите только до 100 коллбэков onChildAdded() . Если в вашей базе данных Firebase хранится менее 100 элементов, коллбэк onChildAdded() будет срабатывать для каждого элемента.
По мере изменения элементов вы получаете коллбэки onChildAdded() для элементов, которые попадают в запрос, и коллбэки onChildRemoved() для элементов, которые из него выпадают, так что общее количество остается равным 100.
В следующем примере показано, как в тестовом приложении для ведения блога определяется запрос для получения списка из 100 самых последних сообщений всех пользователей:
Kotlin
// Last 100 posts, these are automatically the 100 most recent // due to sorting by push() keys. databaseReference.child("posts").limitToFirst(100)
Java
// Last 100 posts, these are automatically the 100 most recent // due to sorting by push() keys Query recentPostsQuery = databaseReference.child("posts") .limitToFirst(100);
В этом примере определен только запрос; для фактической синхронизации данных необходимо прикрепить к нему слушатель .
Фильтрация по ключу или значению
Вы можете использовать startAt() , startAfter() , endAt() , endBefore() и equalTo() для выбора произвольных начальных, конечных и эквивалентных точек для запросов. Это может быть полезно для постраничной навигации данных или поиска элементов с дочерними элементами, имеющими определенное значение.
Как упорядочиваются данные запроса
В этом разделе объясняется, как данные сортируются каждым из методов ORDER BY в классе Query .
orderByChild
При использовании функции orderByChild() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
- В первую очередь идут дочерние элементы, у которых указанный ключ дочернего элемента имеет
nullзначение. - Далее следуют дочерние элементы со значением
falseдля указанного ключа дочернего элемента. Если несколько дочерних элементов имеют значениеfalse, они сортируются лексикографически по ключу. - Далее следуют дочерние элементы, у которых для указанного ключа дочернего элемента значение равно
true. Если несколько дочерних элементов имеют значениеtrue, они сортируются лексикографически по ключу. - Далее следуют дочерние узлы с числовым значением, отсортированные в порядке возрастания. Если несколько дочерних узлов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
- Строки следуют за числами и сортируются лексикографически в порядке возрастания. Если несколько дочерних узлов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
- Объекты располагаются в самом конце и сортируются лексикографически по ключу в порядке возрастания.
orderByKey
При использовании orderByKey() для сортировки данных, данные возвращаются в порядке возрастания по ключу.
- В порядке возрастания первыми идут дочерние элементы, ключ которых может быть интерпретирован как 32-битное целое число.
- Далее следуют дети, у которых в качестве ключа используется строковое значение, отсортированные лексикографически в порядке возрастания.
orderByValue
При использовании orderByValue() дочерние узлы упорядочиваются по их значению. Критерии сортировки такие же, как и в orderByChild() , за исключением того, что вместо значения указанного ключа дочернего узла используется значение самого узла.