Anuncio de Cloud Firestore (Beta): Prueba la nueva base de datos escalable y flexible de Firebase y Google Cloud Platform. Obtén más información sobre Cloud Firestore.

Cómo trabajar con listas de datos en Android

Este documento abarca el trabajo con listas de datos en Firebase. Para conocer los aspectos básicos de lectura y escritura de datos de Firebase, consulta Cómo leer y escribir datos en Android.

Obtén una DatabaseReference

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

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

Lee y escribe listas

Agrega datos a una lista de datos

Usa 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. Es posible usar estas claves generadas automáticamente para cada elemento nuevo de la lista, de manera que varios clientes pueden agregar elementos secundarios a la misma ubicación y al mismo tiempo sin conflictos de escritura. El método push() genera la clave única a partir de una marca de tiempo. Por lo tanto, los elementos de las listas se ordenan cronológicamente de forma automática.

Puedes usar la referencia a los nuevos datos que brinda el método push() para obtener el valor de la clave autogenerada del elemento secundario o definir los datos del elemento secundario. Llamar a getKey() en una referencia de push() da como resultado el valor de la clave generada de forma automática.

Puedes usar estas claves generadas de manera automática para simplificar la compactación de tu estructura de datos. Para obtener más información, consulta el ejemplo de distribución de datos.

Detecta eventos secundarios

Cuando trabajes con listas, tu aplicación debe escuchar eventos secundarios en lugar de los eventos de valor que se usan para objetos individuales.

Los eventos secundarios se activan en respuesta a operaciones específicas que se ejecutan en los elementos secundarios de un nodo a partir de una operación, como un elemento secundario que se agrega 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, en combinación, puede ser útil para escuchar los cambios en un nodo específico de una base de datos.

Para escuchar eventos secundarios en DatabaseReference, agrega un ChildEventListener:

Agente de escucha Devolución de llamada de evento Uso común
ChildEventListener onChildAdded() Recupera listas de elementos o detecta elementos agregados a una lista. Esta devolución de llamada se activa una vez por cada elemento secundario existente y otra vez cuando se agrega un elemento secundario nuevo a la ruta de acceso especificada. La DataSnapshot pasada al agente de escucha contiene los datos del nuevo elemento secundario.
onChildChanged() Detecta cambios en los elementos de una lista. Este evento se activa cada vez que un nodo secundario se modifica, incluidas todas las modificaciones en los descendientes del nodo secundario. La DataSnapshot pasada al agente de escucha de eventos contiene los datos actualizados del elemento secundario.
onChildRemoved() Detecta cuando se quitan elementos de una lista. La DataSnapshot pasada a la devolución de llamada del evento contiene los datos del elemento secundario eliminado.
onChildMoved() Detecta cambios en el orden de los elementos de una lista ordenada. Este evento se activa cada vez que una actualización activa la devolución de llamada onChildChanged() que cambia el orden del elemento secundario. Se usa con datos que se ordenan con orderByChild o orderByValue.

Por ejemplo, una app social de blogs podría usar estos métodos en combinación para supervisar la actividad en los comentarios de una publicación, como se muestra a continuación:

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

Detecta eventos de valores

Si bien se recomienda usar un ChildEventListener para leer listas de datos, hay situaciones en las que agregar un ValueEventListener a una referencia de lista puede ser útil.

Si agregas un ValueEventListener a una lista de datos, el resultado será una lista completa de datos en forma de una sola DataSnapshot. Si le aplicas un bucle a ese resultado, puedes acceder a cada elemento secundario.

Incluso cuando hay una sola coincidencia para la consulta, la instantánea es una lista, aunque contenga un solo elemento. Para acceder al elemento, debes aplicar un bucle al resultado:

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

Este patrón puede ser útil cuando quieres recuperar todos los elementos secundarios de una lista en una sola operación, en lugar de escuchar para detectar eventos onChildAdded adicionales.

Desvincula agentes de escucha

Para borrar las devoluciones de llamada, llama al método removeEventListener() en tu referencia de la base de datos de Firebase.

Si un agente de escucha se agregó varias veces a una ubicación de datos, se llama varias veces para cada evento y debes desvincularlo la misma cantidad de veces para borrarlo por completo.

Si llamas a removeEventListener() en un agente de escucha primario, no se quitan de manera automática los agentes de escucha registrados en sus nodos secundarios. Deberás llamar a removeEventListener() también en todos los agentes de escucha secundarios para borrar la devolución de llamada.

Cómo ordenar y filtrar datos

Puedes usar la clase Query de Realtime Database para recuperar los datos organizados por clave, por valor o por valor del elemento secundario. También puedes filtrar el resultado organizado a una cantidad específica de resultados o un rango de claves o valores.

Ordena los datos

Para recuperar datos ordenados, comienza por especificar uno de los métodos de ordenamiento, a fin de determinar cómo se presentarán los resultados:

Método Uso
orderByChild() Ordena los resultados según el valor de una clave secundaria especificada o una ruta de acceso secundaria anidada.
orderByKey() Ordena los resultados según las claves secundarias.
orderByValue() Ordena los resultados según los valores secundarios.

Puedes usar solo un método de ordenamiento a la vez. Si llamas un método de ordenamiento varias veces en la misma consulta, se genera un error.

El siguiente ejemplo demuestra cómo podrías recuperar una lista de las principales publicaciones de un usuario según la cantidad de estrellas que tienen:

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

Esto define una consulta que, cuando se combina con un agente de escucha de elementos secundarios, sincroniza el cliente con las publicaciones del usuario en la ruta de la base de datos, ordenadas según la cantidad de estrellas que recibió cada publicación. Esta técnica de usar ID como claves de índice se denomina "distribución de datos”. Puedes obtener más información al respecto en Estructura tu base de datos.

La llamada al método orderByChild() especifica la clave secundaria según la que se deben ordenar los resultados. En este caso, las publicaciones se ordenan según el valor del elemento secundario "starCount" respectivo. Las consultas también se pueden ordenar por campos secundarios anidados si tienes datos que lucen de la siguiente forma:

"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 según los valores anidados en la clave metrics. Para ello, debemos especificar la ruta de acceso relativa al elemento secundario anidado presente en nuestra llamada a orderByChild().

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

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

Cómo filtrar datos

Para filtrar datos, puedes combinar cualquiera de los métodos de límite o rango con un método de ordenamiento cuando generes una consulta.

Método Uso
limitToFirst() Configura la cantidad máxima de elementos que pueden mostrarse desde el comienzo de la lista de resultados ordenada.
limitToLast() Define la cantidad máxima de elementos que pueden mostrarse desde el final de la lista de resultados ordenada.
startAt() Muestra elementos con un valor igual o superior a la clave o el valor que se especifica según el método de ordenamiento seleccionado.
endAt() Muestra elementos con un valor inferior o igual a la clave o el valor que se especifica según el método de ordenamiento seleccionado.
equalTo() Muestra elementos con un valor igual a la clave o el valor que se especifica según el método de ordenamiento seleccionado.

A diferencia de los métodos de ordenamiento, las funciones de límite y rango pueden combinarse. Por ejemplo, puedes combinar los métodos startAt() y endAt() para limitar los resultados a un rango de valores específico.

Incluso cuando hay una sola coincidencia para la consulta, la instantánea es una lista, aunque contenga un solo elemento. Para acceder al elemento, debes aplicar un bucle al resultado:

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

Limita la cantidad de resultados

Puedes usar los métodos limitToFirst() y limitToLast() para configurar una cantidad máxima de elementos secundarios que se deben sincronizar para una devolución de llamada específica. Por ejemplo, si usas limitToFirst() para configurar un límite de 100, solo recibirás un máximo de 100 devoluciones de llamada onChildAdded() inicialmente. Si tienes menos de 100 elementos almacenados en la base de datos de Firebase, se activa una devolución de llamada onChildAdded() para cada elemento.

En la medida que los elementos cambian, recibes devoluciones de llamada onChildAdded() por los elementos que ingresan a la consulta y devoluciones de llamada onChildRemoved() por los elementos que quedan fuera, de manera que la cantidad total siga siendo 100.

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

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

En este ejemplo, solo se define una consulta. A fin de sincronizar los datos realmente, se necesitaría un agente de escucha agregado.

Filtra por clave o valor

Puedes usar startAt(), endAt() y equalTo() para seleccionar puntos arbitrarios de inicio, fin y equivalencia para las consultas. Esto puede ser útil para paginar datos o para encontrar elementos con elementos secundarios que tengan un valor específico.

Cómo se ordenan los datos de las consultas

En esta sección, se explica cómo se ordenan los datos en cada uno de los métodos de ordenamiento de la clase Query.

orderByChild

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

  1. Los elementos secundarios cuyas claves secundarias especificadas posean el valor null irán en primer lugar.
  2. A continuación, aparecerán los elementos secundarios que tengan el valor false en la clave secundaria especificada. Si hay varios elementos secundarios con el valor false, se ordenarán lexicográficamente por clave.
  3. A continuación, aparecerán los elementos secundarios que tengan el valor true en la clave secundaria especificada. Si hay varios elementos secundarios con el valor true, se ordenan de manera lexicográfica según la clave.
  4. Luego vienen los elementos secundarios con valor numérico, que se ordenan en sentido ascendente. Si varios elementos secundarios tienen el mismo valor numérico en el nodo secundario especificado, se ordenan según la clave.
  5. Las strings van después de los números y se ordenan de manera lexicográfica, en sentido ascendente. Si varios elementos secundarios tienen el mismo valor en el nodo secundario especificado, se ordenan de manera lexicográfica según la clave.
  6. Los objetos quedan en último lugar y se ordenan de manera lexicográfica según su clave, en orden ascendente.

orderByKey

Cuando ordenas los datos con orderByKey(), los datos se presentan en orden ascendente, según la clave.

  1. Los elementos secundarios con una clave que puede analizarse como un entero de 32 bits van primero, sorios/read-and-write#listen_for_value_eventsted en orden ascendente.
  2. Los campos secundarios con un valor de string como clave van posteriormente y ordenados de manera lexicográfica, en sentido ascendente.

orderByValue

Cuando usas orderByValue(), los elementos secundarios se ordenan según el valor. Los criterios de ordenamiento son los mismos que en orderByChild(), pero se usa el valor del nodo en lugar del valor de una clave secundaria especificada.

Próximos pasos

Enviar comentarios sobre…

Firebase Realtime Database
¿Necesitas ayuda? Visita nuestra página de asistencia.