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

Habilitando recursos offline no Android

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Os aplicativos do Firebase funcionam mesmo que seu aplicativo perca temporariamente a conexão de rede. Além disso, o Firebase fornece ferramentas para persistência de dados localmente, gerenciamento de presença e tratamento de latência.

Persistência de disco

Os aplicativos do Firebase lidam automaticamente com interrupções temporárias de rede. Os dados em cache ficam disponíveis off-line e o Firebase reenvia todas as gravações quando a conectividade de rede é restaurada.

Quando você habilita a persistência de disco, seu aplicativo grava os dados localmente no dispositivo para que seu aplicativo possa manter o estado offline, mesmo que o usuário ou o sistema operacional reinicie o aplicativo.

Você pode habilitar a persistência de disco com apenas uma linha de código.

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

Comportamento de persistência

Ao ativar a persistência, todos os dados que o cliente do Firebase Realtime Database sincronizaria enquanto on-line permanecem no disco e ficam disponíveis off-line, mesmo quando o usuário ou o sistema operacional reinicia o aplicativo. Isso significa que seu aplicativo funciona como online usando os dados locais armazenados no cache. Os retornos de chamada do ouvinte continuarão a ser acionados para atualizações locais.

O cliente do Firebase Realtime Database mantém automaticamente uma fila de todas as operações de gravação executadas enquanto seu aplicativo está off-line. Quando a persistência está habilitada, essa fila também persiste no disco para que todas as suas gravações estejam disponíveis quando o usuário ou o sistema operacional reiniciar o aplicativo. Quando o aplicativo recupera a conectividade, todas as operações são enviadas ao servidor do Firebase Realtime Database.

Se seu aplicativo usa o Firebase Authentication , o cliente Firebase Realtime Database mantém o token de autenticação do usuário nas reinicializações do aplicativo. Se o token de autenticação expirar enquanto seu aplicativo estiver offline, o cliente pausará as operações de gravação até que o aplicativo reautentique o usuário, caso contrário, as operações de gravação poderão falhar devido a regras de segurança.

Mantendo os dados atualizados

O Firebase Realtime Database sincroniza e armazena uma cópia local dos dados para listeners ativos. Além disso, você pode manter locais específicos sincronizados.

Java

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

Kotlin+KTX

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

O cliente do Firebase Realtime Database baixa automaticamente os dados nesses locais e os mantém sincronizados, mesmo que a referência não tenha listeners ativos. Você pode desativar a sincronização novamente com a seguinte linha de código.

Java

scoresRef.keepSynced(false);

Kotlin+KTX

scoresRef.keepSynced(false)

Por padrão, 10 MB de dados sincronizados anteriormente são armazenados em cache. Isso deve ser suficiente para a maioria dos aplicativos. Se o cache ultrapassar o tamanho configurado, o Firebase Realtime Database limpará os dados que foram usados ​​menos recentemente. Os dados mantidos em sincronia não são removidos do cache.

Como consultar dados off-line

O Firebase Realtime Database armazena dados retornados de uma consulta para uso offline. Para consultas construídas offline, o Firebase Realtime Database continua funcionando para dados carregados anteriormente. Se os dados solicitados não forem carregados, o Firebase Realtime Database carregará os dados do cache local. Quando a conectividade de rede estiver disponível novamente, os dados serão carregados e refletirão a consulta.

Por exemplo, este código consulta os últimos quatro itens em um Firebase Realtime Database de pontuações

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

    // ...
})

Suponha que o usuário perca a conexão, fique offline e reinicie o aplicativo. Ainda offline, o aplicativo consulta os dois últimos itens do mesmo local. Essa consulta retornará com êxito os dois últimos itens porque o aplicativo carregou todos os quatro itens na consulta acima.

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

    // ...
})

No exemplo anterior, o cliente do Firebase Realtime Database gera eventos 'filhos adicionados' para os dois dinossauros com pontuação mais alta, usando o cache persistente. Mas não irá gerar um evento de 'valor', pois o aplicativo nunca executou essa consulta enquanto estiver online.

Se o aplicativo solicitasse os últimos seis itens enquanto estiver offline, ele obteria eventos 'filhos adicionados' para os quatro itens armazenados em cache imediatamente. Quando o dispositivo volta a ficar on-line, o cliente do Firebase Realtime Database é sincronizado com o servidor e obtém os dois eventos "filho adicionado" e "valor" finais para o aplicativo.

Manipulação de transações off-line

Todas as transações executadas enquanto o aplicativo está offline são enfileiradas. Depois que o aplicativo recupera a conectividade de rede, as transações são enviadas ao servidor do Realtime Database.

Gerenciando Presença

Em aplicativos em tempo real, geralmente é útil detectar quando os clientes se conectam e desconectam. Por exemplo, você pode marcar um usuário como 'offline' quando o cliente dele se desconectar.

Os clientes do Firebase Database fornecem primitivos simples que podem ser usados ​​para gravar no banco de dados quando um cliente se desconecta dos servidores do Firebase Database. Essas atualizações ocorrem independentemente de o cliente se desconectar corretamente ou não, portanto, você pode contar com elas para limpar os dados mesmo que uma conexão seja interrompida ou um cliente falhe. Todas as operações de gravação, incluindo configuração, atualização e remoção, podem ser executadas em uma desconexão.

Aqui está um exemplo simples de gravação de dados na desconexão usando a 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!")

Como funciona o onDisconnect

Quando você estabelece uma operação onDisconnect() , a operação reside no servidor Firebase Realtime Database. O servidor verifica a segurança para garantir que o usuário possa realizar o evento de gravação solicitado e informa ao seu aplicativo se ele é inválido. O servidor então monitora a conexão. Se em algum momento a conexão atingir o tempo limite ou for ativamente fechada pelo cliente do Realtime Database, o servidor verificará a segurança uma segunda vez (para garantir que a operação ainda seja válida) e, em seguida, invocará o evento.

Seu aplicativo pode usar o retorno de chamada na operação de gravação para garantir que o onDisconnect foi anexado corretamente:

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

Um evento onDisconnect também pode ser cancelado chamando .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()

Detectando o estado da conexão

Para muitos recursos relacionados à presença, é útil que seu aplicativo saiba quando está online ou offline. O Firebase Realtime Database fornece um local especial em /.info/connected , que é atualizado sempre que o estado de conexão do cliente do Firebase Realtime Database muda. Aqui está um exemplo:

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 é um valor booleano que não é sincronizado entre clientes do Realtime Database porque o valor depende do estado do cliente. Em outras palavras, se um cliente lê /.info/connected como falso, isso não é garantia de que um cliente separado também lerá falso.

No Android, o Firebase gerencia automaticamente o estado da conexão para reduzir a largura de banda e o uso da bateria. Quando um cliente não tem listeners ativos, nenhuma gravação pendente ou operações onDisconnect e não é desconectado explicitamente pelo método goOffline , o Firebase fecha a conexão após 60 segundos de inatividade.

Manipulando a latência

Carimbos de data e hora do servidor

Os servidores do Firebase Realtime Database fornecem um mecanismo para inserir carimbos de data/hora gerados no servidor como dados. Esse recurso, combinado com onDisconnect , fornece uma maneira fácil de anotar com segurança a hora em que um cliente do Realtime Database foi desconectado:

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)

Inclinação do relógio

Embora firebase.database.ServerValue.TIMESTAMP seja muito mais preciso e preferível para a maioria das operações de leitura/gravação, às vezes pode ser útil estimar a distorção do relógio do cliente em relação aos servidores do Firebase Realtime Database. Você pode anexar um retorno de chamada ao local /.info/serverTimeOffset para obter o valor, em milissegundos, que os clientes do Firebase Realtime Database adicionam ao horário informado local (tempo de época em milissegundos) para estimar o tempo do servidor. Observe que a precisão desse deslocamento pode ser afetada pela latência da rede e, portanto, é útil principalmente para descobrir grandes discrepâncias (> 1 segundo) no tempo do relógio.

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

Exemplo de aplicativo de presença

Ao combinar operações de desconexão com monitoramento de estado de conexão e registros de data e hora do servidor, você pode criar um sistema de presença do usuário. Nesse sistema, cada usuário armazena dados em um local do banco de dados para indicar se um cliente do Realtime Database está online ou não. Os clientes definem esse local como verdadeiro quando ficam online e um carimbo de data/hora quando se desconectam. Este carimbo de data/hora indica a última vez que um determinado usuário esteve online.

Observe que seu aplicativo deve enfileirar as operações de desconexão antes que um usuário seja marcado como online, para evitar quaisquer condições de corrida caso a conexão de rede do cliente seja perdida antes que ambos os comandos possam ser enviados ao servidor.

Aqui está um sistema simples de presença do usuário:

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