Online first vs Offline first no Flutter: como usar e quando escolher
Se você estuda Flutter, provavelmente já ouviu falar de offline first e online first, né?
O que são essas abordagens? Vem comigo que explico tudo neste artigo!
Entendendo o cenário no desenvolvimento Flutter
Uma pergunta ronda as pessoas desenvolvedoras: É melhor criar um app que depende totalmente da internet (com requisições HTTP, API) ou investir em um produto que funciona bem offline?
A dúvida é válida. Mas precisamos fazer boas perguntas para obter boas respostas. Uma boa pergunta, a ser feita antes, seria: O que acontece no aplicativo quando o usuário perde a conexão?
Os aplicativos, hoje em dia, são quase inseparáveis da internet. Por exemplo, o Uber só chama um carro se você está conectado.
E se a conexão cair? Ficamos à deriva? Imagina: você se perdeu em uma ilha deserta. O seu único recurso é um celular e um aplicativo chamado Wilson.
Parece que sua vida está salva: será possível pedir ajuda ou se virar. Só tem um problema: sem internet, o aplicativo não funciona. Frustrante, né?
Seria melhor se o aplicativo funcionasse offine! Um app Flutter precisa funcionar em um wi-fi potente, um 3G mais lento e, às vezes, na completa ausência de sinal.
No desenvolvimento Flutter, você pode adotar duas estratégias: online first e offline first.
Com online first, a conexão com a internet é priorizada no projeto. Com offline first, o aplicativo funciona desconectado. Agora, vamos entender cada uma delas.
O que é online first?
O online first é uma abordagem em que o aplicativo só funciona conectado à internet. Basicamente, qualquer interação com o app busca dados em tempo real, diretamente de uma API.
Quais são as vantagens do online first?
As principais vantagens são: menor complexidade, dados sempre atualizados e otimização do armazenamento. Vamos entender isso?
No online first, o aplicativo usa mais APIs, simplificando o desenvolvimento. Você se preocupa menos com dados locais, armazenamento e sincronização. O cache vira um recurso temporário, que armazena dados leves.
Outra vantagem: os dados estão sempre atualizados. Se os dados vêm de uma fonte externa, dispensa-se a sincronização manual ou algo do tipo.
E, claro, o online first poupa espaço no dispositivo. Menos dados locais é uma mão na roda no desenvolvimento, e o aplicativo fica mais leve.
Quais são as desvantagens do online first?
A principal desvantagem é a dependência da internet. Caiu a conexão? O app não vai funcionar (ou dar erros).
Isso prejudica a experiência do usuário. Ninguém gosta de ver o app travado — o que também é uma desvantagem.
Quando usar o online first?
Algumas plataformas se beneficiam dessa abordagem: o Instagram e demais redes sociais (Spotify, Netflix e afins).
Em geral, são apps que precisam acessar dados em tempo real para oferecer vídeos, músicas ou conteúdos de redes sociais.
Nesses casos, a experiência só faz sentido com os dados mais recentes e carregados o tempo todo. Agora que entendemos o online first, vamos explorar offline first.
O que é offline first?
O offline first prioriza o uso de dados locais. Ou seja, mesmo sem internet, o aplicativo roda bem.
Se a conexão for restabelecida, o app sincroniza as informações com o servidor, mantendo os dados atualizados nos dois lados.
Assim, o app possui uma base de dados, e o usuário não fica "travado" por falta de internet. A conexão importa, mas sua ausência não impede o uso.
Agora, vamos conhecer as vantagens e desvantagens do offline first.
Quais as vantagens do offline first no Flutter?
A melhora na experiência do usuário. O sinal caiu? O app roda suavemente.
E tem a confiabilidade. O usuário sabe que pode contar com o app a qualquer momento, independente da qualidade da conexão.
Quais as desvantagens do offline first?
O offline first dá mais trabalho ao implementar, pois você precisa eficientemente sincronizar os dados quando a conexão volta.
Um ponto é a consistência dos dados. Alinhar o que está salvo localmente no dispositivo com o que está no servidor pode ser difícil e causar inconsistências.
O que aconteceria se o app se reconecta e “percebe” que as informações locais diferem dos dados no servidor? O que fazemos?
Evitamos esse cenário com as técnicas certas. Uma delas é o timestamp-based merging, que compara os horários das mudanças feitas no app e escolhe automaticamente a versão mais recente. É como olhar dois cadernos e decidir qual tem as anotações mais atuais.
Outra técnica são as operational transforms, que funcionam como um mediador: elas organizam mudanças feitas ao mesmo tempo, garantindo que nenhuma operação "atrapalhe" a outra e que os dados fiquem alinhados.
Quando usar o offline first?
Aplicativos como Kindle, Google Maps e Todoist são bons exemplos. O Kindle possibilita ler os livros baixados de qualquer jeito. O Google Maps salva mapas e direções, então você não se perde na falta de 5G.
O Word, Excel e Google Keep rodam sem internet. Se a conexão é restaurada, todas as alterações são sincronizadas automaticamente.
Agora que você conheceu o offline first, vamos ver como isso se aplica ao Flutter?
Como utilizar online first e offline first no Flutter?
O ecossistema robusto do Flutter oferece pacotes e ferramentas que facilitam o desenvolvimento de aplicativos que exigem internet ou que funcionam desconectados.
Online first no Flutter
Para aplicativos que precisam acessar dados em tempo real, o Flutter simplifica a comunicação com servidores através de pacotes como o http — é uma forma de simplificar as requisições de rede.
Outras bibliotecas, como o Dio, oferecem funcionalidades extras, como tratamento avançado de erros e cache de requisições.
Quer um exemplo prático? Imagine que você precisa buscar informações de um CEP. Usando o pacote HTTP:
import 'package:http/http.dart' as http;
----// Continuação do Código -------/
Future<void> fetchData(String cep) async {
final response = await http.get(Uri.parse('https://viacep.com.br/ws/$cep/json/'));
if (response.statusCode == 200) {
var data = jsonDecode(response.body);
setState(() {
_data = "${data['logradouro']}, ${data['bairro']} - ${data['localidade']}";
});
} else {
setState(() {
_data = "Erro na requisição: ${response.statusCode}";
});
}
}
No código acima, solicitamos para a API da ViaCEP nos devolver informações sobre um endereço, dado o CEP digitado.
Se tudo correr bem, o response.statusCode == 200
traz os dados, do logradouro, bairro e localidade.
Caso contrário, você vai ver uma mensagem de erro. De forma mais direta, essas bibliotecas são práticas, a lógica é desenvolvida nas requisições em si.
Caso queira testar melhor o código de exemplo, deixo aqui o código melhor implementado e pronto para ser executado, de um “RUN’ e teste um CEP:
Caso queira saber mais sobre essa biblioteca, explore o artigo Como obter dados da internet no Flutter usando HTTP.
Offline first no Flutter
Se a tarefa é fazer o aplicativo funcionar desconectado, você trabalha com persistência de dados para que os dados sejam recuperados e utilizados posteriormente.
Para facilitar o desenvolvimento, você pode contar com bibliotecas especializadas em persistência local:
Imagine que você está criando um aplicativo de lista de compras.
A ideia é simples: o usuário adiciona produtos à lista, e mesmo quando o dispositivo está offline, os itens precisam ser armazenados localmente. Esse é um cenário bacana para o Isar, um banco de dados NoSQL extremamente rápido, projetado para funcionar com eficiência tanto em ambientes online quanto offline.
O Isar organiza os dados em coleções. No nosso caso, vamos usar uma coleção chamada ‘caixaProdutos’ para armazenar os produtos da lista de compras.
Isso é muito útil em apps como lista telefônica, aplicativos de anotações, ou qualquer outro cenário em que os dados precisam ser salvos no dispositivo local do usuário, com a possibilidade de sincronização quando a internet estiver disponível novamente.
O processo inicial envolve abrir o banco de dados com o método Isar.open()
. Nele, especificamos o esquema e o diretório onde os dados serão armazenados localmente no dispositivo.
final isar = await Isar.open(
schemas: [ProdutoSchema],
directory: await getApplicationDocumentsDirectory(),
);
Com o banco agora inicializado, podemos definir nossa coleção.
Cada coleção pode ser vista como uma tabela em um banco de dados relacional, mas, no Isar, os dados são armazenados de forma maneira eficiente.
No exemplo abaixo, criamos a classe Produto
, que representa os itens da lista de compras. A classe é anotada com @Collection()
para que o Isar saiba que ela é uma coleção do banco de dados:
@Collection()
class Produto {
Id? id;
late String nome;
}
Agora, com o terreno preparado, vamos interagir com os dados.
Podemos criar funções simples, como uma de adição de produtos na lista. Ela pega o nome do produto, cria um objeto da classe ”Produto”, e armazena no banco de dados local.
Após adicionar o produto, um setState
é bem-vindo para atualizar a interface e refletir a mudança na tela:
class _ProdutoScreenState extends State<ProdutoScreen> {
final TextEditingController _produtoController = TextEditingController();
Future<void> _adicionarProduto(String nomeProduto) async {
final produto = Produto()..nome = nomeProduto;
await isar.writeTxn(() async {
await isar.produtos.put(produto);
});
setState(() {});
_produtoController.clear();
}
}
Se o usuário quiser remover um produto da lista, vamos para a seguinte implementação: criamos a função _removerProduto
.
Ela recebe o ID do produto e o exclui da coleção. O banco de dados é atualizado e a interface é modificada para refletir a exclusão.
Future<void> _removerProduto(int id) async {
await isar.writeTxn(() async {
await isar.produtos.delete(id);
});
setState(() {});
}
}
O código segue para interações com a interface e pode ser visualizado nesse link do gist
Operações simples como essas são a base de um app de lista de compras funcional.
Um diferencial do Isar
é que, enquanto desenvolve, você pode usar o recurso Inspect. Dá para visualizar as coleções e os dados armazenados em tempo real.
Com esse recurso, você consegue interagir com o banco, fazer buscas e ver a estrutura dos dados de uma forma intuitiva, sem precisar escrever mais código.
O que é uma baita “mão na roda”, na hora de desenvolver, pois ajuda a testar, e nesse caso, fica fácil observar se as operações de salvamento e remoção estão funcionando corretamente.
Para ativar o Isar Inspect, é fácil: basta habilitar a opção durante a inicialização do banco de dados. Com isso, você pode explorar visualmente as coleções e seus dados enquanto o aplicativo está em execução.
isar = await Isar.open(
[ProdutoSchema],
directory: dir.path,
inspector: true, // Aqui
);
Agora, com tudo preparado, vamos para o teste. Observe, no gif a seguir, essa interação: o lado esquerdo mostra o inspect do Isar que foi aberto no navegador, pela URL gerada pelo próprio Isar; ao lado direito, o emulador, com uma simples interface desenvolvida, mostra a manipulação dos dados e o uso das funções de adicionar e remover.
Podemos verificar que, através dessa funcionalidade, temos uma maneira eficiente de depurar e garantir que os dados estão sendo manipulados corretamente no banco, sem a necessidade de escrever código extra para visualizá-los.
Se curtiu o Isar, vale a pena conferir o guia de início na ferramenta.
Sincronização de dados
Já vimos que um app offline first precisa sincronizar as informações com o servidor. Como fazer isso no Flutter?
No desenvolvimento Android, você pode usar o WorkManager, que agenda tarefas de segundo plano, atualizando os dados no servidor automaticamente.
Para exemplificar, vamos pensar no código anterior: ele já salva e lista produtos localmente, o que é ótimo para uma aplicação offline.
Mas, em uma aplicação mais completa, se você adicionar produtos no dispositivo 1 enquanto ele está offline, esses produtos serão armazenados apenas localmente.
E quando a internet voltar, o ideal é que o app envie automaticamente essas informações para o servidor.
Assim, ao abrir o app no dispositivo 2, os produtos adicionados no dispositivo 1 serão exibidos, mantendo a sincronização entre todos os dispositivos.
Vamos para a prática? Na inicialização do aplicativo, após configurar o Isar
, chamamos o método initialize
do WorkManager, passando uma função callback responsável por gerenciar a sincronização.
Essa função será invocada sempre que o WorkManager executar a tarefa de sincronização.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await getApplicationDocumentsDirectory();
isar = await Isar.open(
[ProdutoSchema],
directory: dir.path,
);
Workmanager().initialize(despachanteCallback);
runApp(const MyApp());
}
// Callback para o WorkManager
void despachanteCallback() {
Workmanager().executeTask((tarefa, inputData) async {
await sincronizarProdutosComServidor();
return Future.value(true);
});
}
Future<void> sincronizarProdutosComServidor() async {
final produtos = await isar.produtos.where().findAll();
for (var produto in produtos) {
print('Sincronizando ${produto.nome} com o servidor...');
// Aqui seria a lógica de envio ao servidor
}
}
As funções exemplificadas no código têm a finalidade de sincronizar e atualizar o estado da aplicação, baseadas no que está salvo localmente e no servidor.
A despachanteCallback
é chamada automaticamente pelo WorkManager sempre que uma tarefa agendada precisa ser executada.
Já a função sincronizarProdutosComServidor
, tem a tarefa de obter os produtos armazenados localmente no banco de dados Isar e percorre cada item, simulando o envio dos dados para o servidor.
No código, temos um print, mas, num projeto real, funcionaria uma requisição HTTP.
Para sistemas iOS, temos o Background Tasks.
Integrando o WorkManager e Background Tasks à lógica do aplicativo, você garante que as informações estejam sempre atualizadas.
Os usuários ficarão mais satisfeitos com uma experiência mais fluida no offline.
Online first e offline first: qual abordagem escolher?
A sua escolha entre online first e offline first deve considerar as necessidades do aplicativo e o comportamento esperado do usuário. Talvez o offline first pareça a melhor escolha, mas não é bem assim!
Em aplicativos bancários, a segurança é prioridade. É um risco armazenar dados sensíveis no dispositivo.
A maioria das funcionalidades (saldo, transferências) depende de uma conexão. Por isso, os usuários até aceitam que esses apps fiquem limitados ou inoperantes se estão offline — segurança em primeiro lugar!
Agora, se você fosse acampar no meio do mato, passaria maus bocados com um app que depende da internet. Aí, o offline first cai muito bem.
Lembre-se: cada caso é um caso. Mas vejamos algumas orientações gerais.
A online first é ideal para aplicativos que pedem dados em tempo real e se beneficiam da leveza de uma conexão constante.
Redes sociais e serviços de streaming têm uma experiência que só faz sentido com dados sempre atualizados.
O offline first é confiável se o usuário fica sem internet. É ótimo para apps de produtividade ou coleta de dados em campo.
Assim, garantimos o funcionamento e a disponibilidade de informações essenciais, mesmo em locais remotos.
Abordagem híbrida de online e offline first
Por que escolher entre o Tom e o Jerry? A graça é ver os dois juntos.
Em certas situações, uma abordagem híbrida é uma boa escolha. Assim, a falta da conexão não inviabiliza o uso do app, que funciona em online first, mas algumas funcionalidades críticas ficam disponíveis offline.
Por exemplo, em um app de viagens, você acessa mapas ou itinerários, sem internet. Já as atualizações em tempo real, como trânsito ou recomendações de lugares, são carregadas se houver conexão.
O melhor dos dois mundos com Flutter
Com o Flutter, você dispõe de um ecossistema completo para construir apps offline e online first.
Pacotes como http
e Dio
servem para conexão em tempo real; soluções como Isar
e WorkManager
ajudam no armazenamento e sincronização offline. A escolha é sua!
E essa escolha não é apenas técnica, mas estratégica. Compreender o contexto e as necessidades do seu projeto dá insumos para oferecer a melhor experiência ao usuário.
Independentemente do caminho decidido, o Flutter permite explorar o melhor dos dois mundos e criar aplicativos flexíveis e funcionais.
Se bateu aquela curiosidade de saber mais, aproveite para se aprofundar em Flutter:
Desejo todo sucesso na sua trajetória.
Até mais!