Ir para o console

Trabalhar com listas de dados no Android

Este documento abrange o trabalho com listas de dados no Firebase. Para aprender os conceitos básicos de leitura e gravação de dados do Firebase, consulte Ler e gravar dados no Android.

Receber uma referência do banco de dados

Para ler e gravar dados no banco de dados, você precisa de uma instância de DatabaseReference:

Java

private DatabaseReference mDatabase;// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin

private lateinit var database: DatabaseReference// ...
database = FirebaseDatabase.getInstance().reference

Ler e gravar listas

Anexar a uma lista de dados

Use o método push() para anexar dados a uma lista em aplicativos de vários usuários. O método push() gera uma chave exclusiva sempre que um novo filho é adicionado a uma referência específica do Firebase. Ao usar essas chaves geradas automaticamente para cada novo elemento da lista, vários clientes podem adicionar filhos ao mesmo local simultaneamente sem criar conflitos de gravação. A chave exclusiva gerada por push() é baseada em um carimbo de data/hora. Portanto, os itens da lista são organizados automaticamente em ordem cronológica.

Use a referência aos novos dados retornados pelo método push() para receber o valor da chave filha que foi gerada automaticamente ou para definir dados para o filho. Chamar getKey() em uma referência push() retorna o valor da chave gerada automaticamente.

Você pode usar essas chaves para simplificar sua estrutura de dados. Para saber mais, consulte o exemplo de distribuição de dados.

Detectar eventos filhos

Ao trabalhar com listas, seu aplicativo deve detectar eventos filhos em vez de eventos de valor usados para objetos individuais.

Eventos filhos são acionados em resposta a operações específicas que ocorrem nos filhos de um nó de uma operação, como a adição de um novo filho por meio do método push() ou a atualização de um filho pelo método updateChildren(). Juntos, cada um desses métodos pode ser útil para detectar alterações em um nó específico de um banco de dados.

Para detectar eventos de filho em DatabaseReference, anexe um ChildEventListener:

Listener Retorno de chamada de evento Uso normal
ChildEventListener onChildAdded() Recuperar listas de itens ou detectar adições em uma lista de itens. Este retorno de chamada é acionado uma vez para cada filho existente e sempre que um novo filho é adicionado ao caminho especificado. O DataSnapshot passado ao listener contém os dados do novo filho.
onChildChanged() Detectar mudanças em itens de uma lista. Este evento é acionado sempre que um nó filho é modificado, incluindo quaisquer modificações aos descendentes do nó filho. O DataSnapshot passado ao listener de eventos contém os dados atualizados do filho.
onChildRemoved() Detectar itens sendo removidos de uma lista. O DataSnapshot passado ao retorno de chamada do evento contém os dados do filho removido.
onChildMoved() Detectar alterações na classificação dos itens em uma lista ordenada. O evento é acionado sempre que o retorno de chamada onChildChanged() é ativado por uma atualização que causa alteração na ordem do filho. Ele é usado com os dados ordenados com orderByChild ou orderByValue.

Por exemplo, um app de blog de mídia social pode usar esses métodos em conjunto para monitorar atividade nos comentários de uma postagem, conforme mostrado abaixo:

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();
    }
};
ref.addChildEventListener(childEventListener);

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::class.java)

        // ...
    }

    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::class.java)
        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::class.java)
        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)

Detectar eventos de valor

Embora usar um ChildEventListener seja a maneira recomendada de ler listas de dados, existem situações em que a anexação de um ValueEventListener a uma referência de lista é útil.

Anexar um ValueEventListener a uma lista de dados retornará a lista inteira de dados como um DataSnapshot único, que você pode retornar para acessar filhos individuais.

Mesmo quando há apenas uma correspondência para a consulta, o instantâneo ainda é uma lista, mas contém somente um item. Para acessar o item, é necessário retornar o resultado:

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

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())
        // ...
    }
})

Este padrão pode ser útil quando você quiser buscar todos os filhos de uma lista em uma única operação, em vez de detectar eventos onChildAdded adicionais.

Remover listeners

Retornos de chamada são removidos chamando o método removeEventListener() na sua referência ao banco de dados do Firebase.

Se um listener tiver sido adicionado várias vezes em um local de dados, ele será chamado várias vezes para cada evento, e você precisa desanexá-lo o mesmo número de vezes para removê-lo por completo.

Chamar removeEventListener() em um listener pai não remove automaticamente os listeners registrados nos nós filhos dele. removeEventListener() também precisa ser chamado nos listeners filhos para remover o retorno de chamada.

Classificar e filtrar dados

Use a classe Query do Realtime Database para recuperar dados classificados por chave, valor ou valor do filho. Também é possível filtrar o resultado classificado por um número específico de resultados ou um intervalo de chaves ou valores.

Classificar dados

Para recuperar dados classificados, comece especificando um dos métodos de ordenação para determinar como os resultados são ordenados:

Método Uso
orderByChild() Ordenar resultados pelo valor de uma chave filha específica ou caminho filho aninhado.
orderByKey() Ordenar resultados por chaves filhas.
orderByValue() Ordenar resultados por valores filhos.

Você pode usar somente um método de ordenação por vez. Chamar um método de ordenação várias vezes na mesma consulta causa um erro.

O exemplo a seguir demonstra como você pode recuperar uma lista das principais postagens de um usuário ordenadas pela contagem de estrelas:

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
    // ...
});

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
    // ...
})

Isso define uma consulta que, quando combinada com um listener filho, sincroniza o cliente com as postagens do usuário do caminho no banco de dados com base no código do usuário dele, ordenado pelo número de estrelas que cada postagem recebeu. Essa técnica de usar códigos como chaves de indexação é chamada de distribuição de dados e é explicada com mais detalhes em Estruturar seu banco de dados.

A chamada para o método orderByChild() especifica a chave filha pela qual ordenar os resultados. Nesse caso, as postagens são classificadas pelo valor do respectivo "starCount" filho. As consultas também podem ser ordenadas por filhos aninhados, caso seus dados se pareçam com estes:

"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",
  }
},

Nesse exemplo, podemos ordenar nossos elementos de lista por valores aninhados na chave metrics, especificando o caminho relacionado ao filho aninhado em nossa chamada de orderByChild().

Java

// Most viewed posts
Query myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views");
myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

Kotlin

// Most viewed posts
val myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views")
myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Para mais informações sobre a ordenação de outros tipos de dados, consulte Como os dados de consulta são ordenados.

Filtrar dados

Para filtrar dados, combine um dos métodos de limite ou de intervalo com um método de ordenação ao criar uma consulta.

Método Uso
limitToFirst() Definir o número máximo de itens para retornar a partir do início da lista ordenada de resultados.
limitToLast() Definir o número máximo de itens para retornar a partir do fim da lista ordenada de resultados.
startAt() Retornar itens maiores ou iguais à chave ou ao valor especificado, dependendo do método de ordenação escolhido.
endAt() Retornar itens menores ou iguais à chave ou ao valor especificado, dependendo do método de ordenação escolhido.
equalTo() Retornar itens iguais à chave ou ao valor especificado, dependendo do método de ordenação escolhido.

Ao contrário dos métodos de ordenação, você pode combinar várias funções de limite ou de intervalo. Por exemplo, combine os métodos startAt() e endAt() para limitar os resultados a um intervalo especificado de valores.

Mesmo quando há apenas uma correspondência para a consulta, o instantâneo ainda é uma lista, mas contém somente um item. Para acessar o item, é necessário retornar o resultado:

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

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())
        // ...
    }
})

Limitar o número de resultados

Use os métodos limitToFirst() e limitToLast() para definir um número máximo de filhos a serem sincronizados para determinado retorno de chamada. Por exemplo, se você usar limitToFirst() para definir um limite de 100, inicialmente receberá até 100 retornos de chamada onChildAdded(). Se você tiver menos de 100 itens armazenados no seu banco de dados do Firebase, um retorno de chamada onChildAdded() será acionado para cada item.

Conforme os itens são alterados, você recebe retornos de chamada onChildAdded() para os itens presentes na consulta e retornos de chamada onChildRemoved() para os itens que saem da consulta para que o número total permaneça 100.

Veja a seguir uma demonstração de como o exemplo do app de blog define uma consulta para recuperar uma lista das 100 postagens mais recentes de todos os usuários:

Java

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
        .limitToFirst(100);

Kotlin

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys.
return databaseReference.child("posts")
        .limitToFirst(100)

Esse exemplo define somente uma consulta. Para sincronizar os dados, é necessário ter um listener anexado.

Filtrar por chave ou valor

Use startAt(), endAt() e equalTo() para escolher pontos arbitrários de início, fim e equivalência para consultas. Isso pode ser útil para paginar dados ou encontrar itens com filhos que tenham um valor específico.

Como os dados de consultas são ordenados

Nesta seção, explicamos como os dados são classificados por cada um dos métodos de ordenação na classe Query.

orderByChild

Ao usar orderByChild(), os dados que contêm a chave filha especificada são ordenados desta maneira:

  1. Filhos com valor null para a chave filha especificada vêm primeiro.
  2. Filhos com um valor false para a chave filha especificada são os próximos. Se vários filhos tiverem o valor false, eles serão classificados lexicograficamente por chave.
  3. Filhos com um valor true para a chave filha especificada são os próximos. Se vários filhos tiverem um valor true, eles serão classificados lexicograficamente pela chave.
  4. Filhos com um valor numérico são os próximos, classificados em ordem crescente. Se vários filhos tiverem o mesmo valor numérico para o nó filho especificado, eles serão classificados por chave.
  5. Strings vêm depois dos números e são classificadas lexicograficamente em ordem crescente. Se vários filhos tiverem o mesmo valor para o nó filho especificado, eles serão ordenados lexicograficamente por chave.
  6. Objetos são os últimos e são classificados lexicograficamente pela chave em ordem crescente.

orderByKey

Ao usar orderByKey() para classificar seus dados, os dados são retornados em ordem ascendente pelo nome da chave.

  1. Filhos com uma chave que possa ser analisada como um número inteiro de 32 bits vêm primeiro, classificados em ordem ascendente.
  2. Filhos com um valor de string como chave são os próximos, classificados de modo lexicográfico em ordem ascendente.

orderByValue

Ao usar orderByValue(), os filhos são ordenados pelo valor deles. Os critérios de ordenação são os mesmos que os em orderByChild(), mas o valor do nó é usado em vez do valor de uma chave filha especificada.

Próximas etapas