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

Habilitación de capacidades sin conexión en Android

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

Las aplicaciones de Firebase funcionan incluso si su aplicación pierde temporalmente su conexión de red. Además, Firebase proporciona herramientas para conservar los datos localmente, administrar la presencia y controlar la latencia.

Persistencia de disco

Las aplicaciones de Firebase manejan automáticamente las interrupciones temporales de la red. Los datos almacenados en caché están disponibles sin conexión y Firebase reenvía cualquier escritura cuando se restaura la conectividad de la red.

Cuando habilita la persistencia del disco, su aplicación escribe los datos localmente en el dispositivo para que su aplicación pueda mantener el estado sin conexión, incluso si el usuario o el sistema operativo reinician la aplicación.

Puede habilitar la persistencia del disco con solo una línea de código.

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

Comportamiento de persistencia

Al habilitar la persistencia, cualquier dato que el cliente de Firebase Realtime Database sincronice mientras está en línea persiste en el disco y está disponible sin conexión, incluso cuando el usuario o el sistema operativo reinician la aplicación. Esto significa que su aplicación funciona como lo haría en línea utilizando los datos locales almacenados en el caché. Las devoluciones de llamada de los oyentes continuarán activando actualizaciones locales.

El cliente de Firebase Realtime Database automáticamente mantiene una cola de todas las operaciones de escritura que se realizan mientras su aplicación está sin conexión. Cuando la persistencia está habilitada, esta cola también se conserva en el disco para que todas sus escrituras estén disponibles cuando el usuario o el sistema operativo reinicie la aplicación. Cuando la aplicación recupera la conectividad, todas las operaciones se envían al servidor de Firebase Realtime Database.

Si su aplicación usa Firebase Authentication , el cliente de Firebase Realtime Database conserva el token de autenticación del usuario en los reinicios de la aplicación. Si el token de autenticación caduca mientras su aplicación está sin conexión, el cliente detiene las operaciones de escritura hasta que su aplicación vuelva a autenticar al usuario; de lo contrario, las operaciones de escritura pueden fallar debido a las reglas de seguridad.

Mantener los datos actualizados

Firebase Realtime Database sincroniza y almacena una copia local de los datos para los oyentes activos. Además, puede mantener sincronizadas ubicaciones específicas.

Java

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);

Kotlin+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.keepSynced(true)

El cliente de Firebase Realtime Database descarga automáticamente los datos en estas ubicaciones y los mantiene sincronizados incluso si la referencia no tiene oyentes activos. Puede volver a desactivar la sincronización con la siguiente línea de código.

Java

scoresRef.keepSynced(false);

Kotlin+KTX

scoresRef.keepSynced(false)

De forma predeterminada, se almacenan en caché 10 MB de datos sincronizados previamente. Esto debería ser suficiente para la mayoría de las aplicaciones. Si el caché supera su tamaño configurado, Firebase Realtime Database purga los datos que se han usado menos recientemente. Los datos que se mantienen sincronizados no se eliminan de la memoria caché.

Consulta de datos sin conexión

Firebase Realtime Database almacena los datos devueltos por una consulta para usarlos sin conexión. Para las consultas creadas sin conexión, Firebase Realtime Database sigue funcionando para los datos cargados previamente. Si los datos solicitados no se cargaron, Firebase Realtime Database carga los datos de la memoria caché local. Cuando la conectividad de la red vuelva a estar disponible, los datos se cargarán y reflejarán la consulta.

Por ejemplo, este código consulta los últimos cuatro elementos en una base de datos de puntajes en tiempo real de Firebase.

Java

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

Kotlin+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

Suponga que el usuario pierde la conexión, se desconecta y reinicia la aplicación. Mientras aún está fuera de línea, la aplicación consulta los dos últimos elementos desde la misma ubicación. Esta consulta devolverá correctamente los dos últimos elementos porque la aplicación había cargado los cuatro elementos en la consulta anterior.

Java

scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

Kotlin+KTX

scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

En el ejemplo anterior, el cliente de Firebase Realtime Database genera eventos "secundarios agregados" para los dos dinosaurios con la puntuación más alta mediante el uso de la memoria caché persistente. Pero no generará un evento de 'valor', ya que la aplicación nunca ejecutó esa consulta mientras estaba en línea.

Si la aplicación solicitara los últimos seis elementos mientras está fuera de línea, obtendría eventos de 'niño agregado' para los cuatro elementos almacenados en caché de inmediato. Cuando el dispositivo vuelve a estar en línea, el cliente de Firebase Realtime Database se sincroniza con el servidor y obtiene los dos últimos eventos "secundarios" y "valor" para la aplicación.

Manejo de transacciones fuera de línea

Todas las transacciones que se realizan mientras la aplicación está fuera de línea se ponen en cola. Una vez que la aplicación recupera la conectividad de la red, las transacciones se envían al servidor de Realtime Database.

Gestión de presencia

En las aplicaciones en tiempo real, suele ser útil detectar cuándo se conectan y desconectan los clientes. Por ejemplo, puede marcar a un usuario como 'fuera de línea' cuando su cliente se desconecta.

Los clientes de Firebase Database proporcionan primitivas simples que puede usar para escribir en la base de datos cuando un cliente se desconecta de los servidores de Firebase Database. Estas actualizaciones ocurren ya sea que el cliente se desconecte limpiamente o no, por lo que puede confiar en ellas para limpiar los datos incluso si se interrumpe una conexión o si un cliente falla. Todas las operaciones de escritura, incluidas la configuración, la actualización y la eliminación, se pueden realizar tras una desconexión.

Aquí hay un ejemplo simple de escribir datos al desconectarse usando la primitiva onDisconnect :

Java

DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");

Kotlin+KTX

val presenceRef = Firebase.database.getReference("disconnectmessage")
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!")

Cómo funciona onDisconnect

Cuando establece una operación onDisconnect() , la operación vive en el servidor de Firebase Realtime Database. El servidor verifica la seguridad para asegurarse de que el usuario pueda realizar el evento de escritura solicitado e informa a su aplicación si no es válida. A continuación, el servidor supervisa la conexión. Si en algún momento se agota el tiempo de espera de la conexión o el cliente de Realtime Database la cierra activamente, el servidor comprueba la seguridad por segunda vez (para asegurarse de que la operación sigue siendo válida) y luego invoca el evento.

Su aplicación puede usar la devolución de llamada en la operación de escritura para asegurarse de que onDisconnect se adjuntó correctamente:

Java

presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() {
    @Override
    public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) {
        if (error != null) {
            Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage());
        }
    }
});

Kotlin+KTX

presenceRef.onDisconnect().removeValue { error, reference ->
    error?.let {
        Log.d(TAG, "could not establish onDisconnect event: ${error.message}")
    }
}

Un evento onDisconnect también se puede cancelar llamando a .cancel() :

Java

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.setValue("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

Kotlin+KTX

val onDisconnectRef = presenceRef.onDisconnect()
onDisconnectRef.setValue("I disconnected")
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel()

Detección del estado de la conexión

Para muchas funciones relacionadas con la presencia, es útil que su aplicación sepa cuándo está en línea o fuera de línea. Firebase Realtime Database proporciona una ubicación especial en /.info/connected que se actualiza cada vez que cambia el estado de conexión del cliente de Firebase Realtime Database. Aquí hay un ejemplo:

Java

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            Log.d(TAG, "connected");
        } else {
            Log.d(TAG, "not connected");
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

Kotlin+KTX

val connectedRef = Firebase.database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue(Boolean::class.java) ?: false
        if (connected) {
            Log.d(TAG, "connected")
        } else {
            Log.d(TAG, "not connected")
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

/.info/connected es un valor booleano que no está sincronizado entre clientes de Realtime Database porque el valor depende del estado del cliente. En otras palabras, si un cliente lee /.info/connected como falso, esto no garantiza que otro cliente también lea falso.

En Android, Firebase administra automáticamente el estado de la conexión para reducir el ancho de banda y el uso de la batería. Cuando un cliente no tiene agentes de escucha activos, ni operaciones de escritura o de desconexión pendientes, y el método onDisconnect no lo desconecta goOffline , Firebase cierra la conexión después de 60 segundos de inactividad.

Latencia de manejo

Marcas de tiempo del servidor

Los servidores de Firebase Realtime Database proporcionan un mecanismo para insertar marcas de tiempo generadas en el servidor como datos. Esta función, combinada con onDisconnect , proporciona una manera fácil de tomar nota de manera confiable del momento en que se desconectó un cliente de Realtime Database:

Java

DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

Kotlin+KTX

val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline")
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

Desviación del reloj

Si bien firebase.database.ServerValue.TIMESTAMP es mucho más preciso y preferible para la mayoría de las operaciones de lectura/escritura, ocasionalmente puede ser útil para estimar el sesgo del reloj del cliente con respecto a los servidores de Firebase Realtime Database. Puede adjuntar una devolución de llamada a la ubicación /.info/serverTimeOffset para obtener el valor, en milisegundos, que los clientes de Firebase Realtime Database agregan a la hora local informada (tiempo de época en milisegundos) para estimar la hora del servidor. Tenga en cuenta que la precisión de este desplazamiento puede verse afectada por la latencia de la red y, por lo tanto, es útil principalmente para descubrir grandes discrepancias (> 1 segundo) en la hora del reloj.

Java

DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        double offset = snapshot.getValue(Double.class);
        double estimatedServerTimeMs = System.currentTimeMillis() + offset;
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

Kotlin+KTX

val offsetRef = Firebase.database.getReference(".info/serverTimeOffset")
offsetRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val offset = snapshot.getValue(Double::class.java) ?: 0.0
        val estimatedServerTimeMs = System.currentTimeMillis() + offset
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

Ejemplo de aplicación de presencia

Al combinar las operaciones de desconexión con la supervisión del estado de la conexión y las marcas de tiempo del servidor, puede crear un sistema de presencia del usuario. En este sistema, cada usuario almacena datos en una ubicación de base de datos para indicar si un cliente de Realtime Database está en línea o no. Los clientes configuran esta ubicación como verdadera cuando se conectan y una marca de tiempo cuando se desconectan. Esta marca de tiempo indica la última vez que el usuario dado estuvo en línea.

Tenga en cuenta que su aplicación debe poner en cola las operaciones de desconexión antes de que un usuario se marque en línea, para evitar cualquier condición de carrera en caso de que se pierda la conexión de red del cliente antes de que ambos comandos puedan enviarse al servidor.

Aquí hay un sistema de presencia de usuario simple:

Java

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline");

final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            DatabaseReference con = myConnectionsRef.push();

            // When this device disconnects, remove it
            con.onDisconnect().removeValue();

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(Boolean.TRUE);
        }
    }

    @Override
    public void onCancelled(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

Kotlin+KTX

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
val database = Firebase.database
val myConnectionsRef = database.getReference("users/joe/connections")

// Stores the timestamp of my last disconnect (the last time I was seen online)
val lastOnlineRef = database.getReference("/users/joe/lastOnline")

val connectedRef = database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue<Boolean>() ?: false
        if (connected) {
            val con = myConnectionsRef.push()

            // When this device disconnects, remove it
            con.onDisconnect().removeValue()

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(java.lang.Boolean.TRUE)
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled at .info/connected")
    }
})