Работа со списками данных на Android

В этом документе рассказывается, как работать со списками данных в Firebase. О том, как читать и записывать данные Firebase, рассказывается в статье Чтение и запись данных на устройствах Android.

Получение DatabaseReference

Чтобы читать и записывать данные в базу данных, вам нужен экземпляр 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
    // ...
});

Этот запрос в сочетании с дочерним прослушивателем синхронизирует клиент с записями пользователя из пути в базе данных на основе его идентификатора пользователя, упорядоченными по количеству звезд, полученных каждой записью. Этот метод использования идентификаторов в качестве ключей индекса называется разветвлением данных. Подробнее о нем можно прочитать в статье Структурирование базы данных.

Вызов метода 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(), чтобы задавать произвольные начальные, конечные и эквивалентные точки для запросов. Это может быть полезно для разбивки данных на страницы или поиска элементов с дочерними элементами, имеющими определенное значение.

Как упорядочиваются данные запроса

В этом разделе объясняется, как сортируются данные при использовании каждого из способов сортировки в классе Query.

orderByChild

При использовании orderByChild() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:

  1. Дочерние элементы со значением null для указанного дочернего ключа идут первыми.
  2. Затем следуют дочерние элементы со значением false для указанного дочернего ключа. Если у нескольких дочерних элементов значение false совпадает, они сортируются по ключу в лексикографическом порядке.
  3. Затем следуют дочерние элементы со значением true для указанного дочернего ключа. Если у нескольких дочерних элементов значение true, они сортируются по ключу в лексикографическом порядке.
  4. Затем идут дочерние элементы с числовыми значениями, отсортированные по возрастанию. Если у нескольких дочерних узлов одинаковое числовое значение, они сортируются по ключу.
  5. Строки следуют за числами и сортируются в лексикографическом порядке по возрастанию. Если у нескольких дочерних узлов одинаковое значение, они сортируются по ключу в лексикографическом порядке.
  6. Объекты указываются в последнюю очередь и сортируются в лексикографическом порядке по ключу в порядке возрастания.

orderByKey

При использовании orderByKey() для сортировки данные возвращаются в порядке возрастания по ключу.

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

orderByValue

При использовании orderByValue() дочерние элементы упорядочиваются по значению. Критерии сортировки такие же, как и в orderByChild(), но вместо значения указанного дочернего ключа используется значение узла.

Дальнейшие действия