Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Trabajar con listas de datos en Android

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Este documento cubre el trabajo con listas de datos en Firebase. Para conocer los aspectos básicos de la lectura y escritura de datos de Firebase, consulte Lectura y escritura de datos en Android .

Obtener una referencia de base de datos

Para leer y escribir datos de la base de datos, necesita una instancia de DatabaseReference :

Java

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

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Leer y escribir listas

Agregar a una lista de datos

Utilice el método push() para agregar datos a una lista en aplicaciones multiusuario. El método push() genera una clave única cada vez que se agrega un nuevo elemento secundario a la referencia de Firebase especificada. Mediante el uso de estas claves generadas automáticamente para cada nuevo elemento de la lista, varios clientes pueden agregar niños a la misma ubicación al mismo tiempo sin conflictos de escritura. La clave única generada por push() se basa en una marca de tiempo, por lo que los elementos de la lista se ordenan cronológicamente automáticamente.

Puede usar la referencia a los nuevos datos devueltos por el método push() para obtener el valor de la clave generada automáticamente del niño o establecer datos para el niño. Llamar getKey() en una referencia push() devuelve el valor de la clave generada automáticamente.

Puede usar estas claves generadas automáticamente para simplificar el aplanamiento de su estructura de datos. Para obtener más información, consulte el ejemplo de despliegue de datos.

Escuche los eventos de los niños

Cuando trabaje con listas, su aplicación debe escuchar eventos secundarios en lugar de eventos de valor utilizados para objetos individuales.

Los eventos secundarios se desencadenan en respuesta a operaciones específicas que les suceden a los elementos secundarios de un nodo a partir de una operación, como un nuevo elemento secundario agregado a través del método push() o un elemento secundario que se actualiza a través del método updateChildren() . Cada uno de estos juntos puede ser útil para escuchar cambios en un nodo específico en una base de datos.

Para escuchar eventos secundarios en DatabaseReference , adjunte un ChildEventListener :

Oyente Devolución de llamada de evento Uso típico
ChildEventListener onChildAdded() Recupere listas de elementos o escuche adiciones a una lista de elementos. Esta devolución de llamada se activa una vez para cada elemento secundario existente y luego nuevamente cada vez que se agrega un elemento secundario nuevo a la ruta especificada. El DataSnapshot pasado al oyente contiene los datos del nuevo niño.
onChildChanged() Escuche los cambios en los elementos de una lista. Este evento se activa cada vez que se modifica un nodo secundario, incluidas las modificaciones a los descendientes del nodo secundario. La DataSnapshot de datos que se pasa al detector de eventos contiene los datos actualizados del elemento secundario.
onChildRemoved() Escuche los elementos que se eliminan de una lista. La DataSnapshot de datos pasada a la devolución de llamada del evento contiene los datos del elemento secundario eliminado.
onChildMoved() Escuche los cambios en el orden de los elementos en una lista ordenada. Este evento se desencadena cada vez que la devolución de llamada onChildChanged() se desencadena por una actualización que provoca el reordenamiento del elemento secundario. Se utiliza con datos que se ordenan con orderByChild o orderByValue .

Por ejemplo, una aplicación de blogs sociales podría usar estos métodos juntos para monitorear la actividad en los comentarios de una publicación, como se muestra a continuación:

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);

Kotlin+KTX

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)

Escuche los eventos de valor

Si bien el uso de ChildEventListener es la forma recomendada de leer listas de datos, hay situaciones en las que es útil adjuntar un ValueEventListener a una referencia de lista.

Adjuntar un ValueEventListener a una lista de datos devolverá la lista completa de datos como un solo DataSnapshot , que luego puede recorrer para acceder a niños individuales.

Incluso cuando solo hay una única coincidencia para la consulta, la instantánea sigue siendo una lista; solo contiene un solo elemento. Para acceder al elemento, debe recorrer el 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+KTX

// 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 patrón puede ser útil cuando desea obtener todos los elementos secundarios de una lista en una sola operación, en lugar de escuchar eventos onChildAdded adicionales.

Separar oyentes

Las devoluciones de llamada se eliminan llamando al método removeEventListener() en la referencia de la base de datos de Firebase.

Si se ha agregado un agente de escucha varias veces a una ubicación de datos, se lo llama varias veces para cada evento y debe separarlo la misma cantidad de veces para eliminarlo por completo.

Llamar a removeEventListener() en un agente de escucha principal no elimina automáticamente los agentes de escucha registrados en sus nodos secundarios; removeEventListener() también debe llamarse en cualquier oyente secundario para eliminar la devolución de llamada.

Ordenar y filtrar datos

Puede usar la clase de Query de base de datos en tiempo real para recuperar datos ordenados por clave, por valor o por valor de un elemento secundario. También puede filtrar el resultado ordenado a un número específico de resultados o un rango de claves o valores.

Ordenar datos

Para recuperar datos ordenados, comience especificando uno de los métodos de ordenación para determinar cómo se ordenan los resultados:

Método Uso
orderByChild() Ordene los resultados por el valor de una clave secundaria especificada o una ruta secundaria anidada.
orderByKey() Ordene los resultados por claves secundarias.
orderByValue() Ordene los resultados por valores secundarios.

Solo puede usar un método de ordenar a la vez. Llamar a un método order-by varias veces en la misma consulta genera un error.

El siguiente ejemplo demuestra cómo puede recuperar una lista de las publicaciones principales de un usuario ordenadas por su número de estrellas:

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+KTX

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

Esto define una consulta que, cuando se combina con un oyente secundario, sincroniza el cliente con las publicaciones del usuario desde la ruta en la base de datos en función de su ID de usuario, ordenadas por la cantidad de estrellas que ha recibido cada publicación. Esta técnica de usar ID como claves de índice se llama despliegue de datos, puede leer más sobre esto en Estructura de su base de datos .

La llamada al método orderByChild() especifica la clave secundaria por la que ordenar los resultados. En este caso, las publicaciones se ordenan por el valor de su respectivo hijo "starCount" . Las consultas también se pueden ordenar por hijos anidados, en caso de que tenga datos como este:

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

En este ejemplo, podemos ordenar los elementos de nuestra lista por valores anidados bajo la clave de metrics especificando la ruta relativa al elemento secundario anidado en nuestra llamada 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+KTX

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

Para obtener más información sobre cómo se ordenan otros tipos de datos, consulte Cómo se ordenan los datos de consulta .

Filtrado de datos

Para filtrar datos, puede combinar cualquiera de los métodos de límite o rango con un método de ordenación al construir una consulta.

Método Uso
limitToFirst() Establece el número máximo de elementos que se devolverán desde el principio de la lista ordenada de resultados.
limitToLast() Establece el número máximo de elementos que se devolverán desde el final de la lista ordenada de resultados.
startAt() Devuelve elementos mayores o iguales que la clave o el valor especificado según el método de ordenación elegido.
startAfter() Devuelve elementos mayores que la clave o el valor especificados según el método de orden elegido.
endAt() Devuelve artículos menores o iguales a la clave o valor especificado según el método de orden elegido.
endBefore() Devuelve artículos con una clave o valor inferior a los especificados según el método de ordenación elegido.
equalTo() Devuelve elementos iguales a la clave o valor especificado según el método de orden elegido.

A diferencia de los métodos order-by, puede combinar múltiples funciones de límite o rango. Por ejemplo, puede combinar los startAt() y endAt() para limitar los resultados a un rango específico de valores.

Incluso cuando solo hay una única coincidencia para la consulta, la instantánea sigue siendo una lista; solo contiene un solo elemento. Para acceder al elemento, debe recorrer el 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+KTX

// 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 el número de resultados

Puede usar los limitToFirst() y limitToLast() para establecer una cantidad máxima de elementos secundarios que se sincronizarán para una devolución de llamada determinada. Por ejemplo, si usa limitToFirst() para establecer un límite de 100, inicialmente solo recibirá hasta 100 devoluciones de llamada onChildAdded() . Si tiene menos de 100 elementos almacenados en su base de datos de Firebase, se activa una devolución de llamada onChildAdded() para cada elemento.

A medida que los elementos cambian, recibe devoluciones de llamada de onChildAdded() para los elementos que ingresan a la consulta y devoluciones de llamada de onChildRemoved() para los elementos que la abandonan para que el número total permanezca en 100.

El siguiente ejemplo demuestra cómo la aplicación de blogs de ejemplo define una consulta para recuperar una lista de las 100 publicaciones más recientes de todos los usuarios:

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+KTX

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

Este ejemplo solo define una consulta, para sincronizar datos realmente necesita tener un oyente adjunto.

Filtrar por clave o valor

Puede usar startAt() , startAfter() , endAt() , endBefore() y equalTo() para elegir puntos de equivalencia, inicio y finalización arbitrarios para las consultas. Esto puede ser útil para paginar datos o encontrar elementos con elementos secundarios que tengan un valor específico.

Cómo se ordenan los datos de consulta

Esta sección explica cómo se ordenan los datos por cada uno de los métodos order-by en la clase Query .

orderByChild

Cuando se usa orderByChild() , los datos que contienen la clave secundaria especificada se ordenan de la siguiente manera:

  1. Los elementos secundarios con un valor null para la clave secundaria especificada vienen primero.
  2. Los elementos secundarios con un valor false para la clave secundaria especificada vienen a continuación. Si varios hijos tienen un valor de false , se ordenan lexicográficamente por clave.
  3. Los elementos secundarios con un valor de true para la clave secundaria especificada vienen a continuación. Si varios hijos tienen un valor de true , se ordenan lexicográficamente por clave.
  4. Los niños con un valor numérico vienen a continuación, ordenados en orden ascendente. Si varios elementos secundarios tienen el mismo valor numérico para el nodo secundario especificado, se ordenan por clave.
  5. Las cadenas vienen después de los números y se ordenan lexicográficamente en orden ascendente. Si varios hijos tienen el mismo valor para el nodo hijo especificado, se ordenan lexicográficamente por clave.
  6. Los objetos van en último lugar y se ordenan lexicográficamente por clave en orden ascendente.

orderByKey

Cuando usa orderByKey() para ordenar sus datos, los datos se devuelven en orden ascendente por clave.

  1. Los elementos secundarios con una clave que se puede analizar como un entero de 32 bits aparecen primero, ordenados en orden ascendente.
  2. Los niños con un valor de cadena como clave vienen a continuación, clasificados lexicográficamente en orden ascendente.

orderByValue

Cuando se usa orderByValue() , los elementos secundarios se ordenan por su valor. Los criterios de ordenación son los mismos que en orderByChild() , excepto que se usa el valor del nodo en lugar del valor de una clave secundaria especificada.

Próximos pasos