В этом документе рассматривается работа со списками данных в 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() | Сортировать результаты по дочерним значениям. |
Одновременно можно использовать только один метод order-by. Многократный вызов метода order-by в одном запросе приводит к ошибке.
В следующем примере показано, как можно получить список лучших публикаций пользователя, отсортированный по количеству звезд:
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() | Возвращает элементы, равные указанному ключу или значению, в зависимости от выбранного метода сортировки. |
В отличие от методов order-by, вы можете комбинировать несколько функций ограничения или диапазона. Например, можно объединить методы 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()
для выбора произвольных начальных, конечных точек и точек эквивалентности для запросов. Это может быть полезно для разбиения данных на страницы или поиска элементов с дочерними элементами, имеющими определённое значение.
Как упорядочиваются данные запроса
В этом разделе объясняется, как данные сортируются каждым из методов сортировки в классе Query
.
orderByChild
При использовании orderByChild()
данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
- Сначала идут дочерние элементы с
null
значением для указанного дочернего ключа. - Далее идут дочерние элементы со значением
false
для указанного ключа. Если значениеfalse
есть у нескольких дочерних элементов, они сортируются лексикографически по ключу. - Далее идут дочерние элементы со значением
true
для указанного дочернего ключа. Если значениеtrue
есть у нескольких дочерних элементов, они сортируются лексикографически по ключу. - Далее следуют дочерние элементы с числовым значением, отсортированные по возрастанию. Если несколько дочерних элементов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
- Строки следуют за числами и сортируются лексикографически по возрастанию. Если несколько дочерних элементов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
- Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.
orderByKey
При использовании orderByKey()
для сортировки данных данные возвращаются в порядке возрастания ключа.
- Сначала идут потомки с ключом, который можно проанализировать как 32-битное целое число, отсортированные по возрастанию.
- Далее следуют дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.
orderByValue
При использовании orderByValue()
дочерние элементы сортируются по их значению. Критерии упорядочивания те же, что и в orderByChild()
, за исключением того, что вместо значения указанного дочернего ключа используется значение узла.