App mobile offline-first: React Native sem ilusão de rede

Publicado em

Um app mobile bem projetado não pergunta se o usuário ficará sem internet; ele assume que isso vai acontecer e define o que continua funcionando. Em React Native ou nativo, offline-first significa tratar cache, fila de ações, sincronização e conflitos como parte da experiência principal, não como remendo para quando a API falha.

Quando um app precisa ser offline-first?

Offline-first vale quando a tarefa do usuário não pode depender de uma conexão perfeita. Isso inclui checklists em lojas, visitas técnicas, captura de documentos, pedidos em campo, assinatura de contratos, apps de delivery, prontuários, CRM externo e qualquer fluxo usado em metrô, estrada, galpão, hospital ou evento lotado.

Um desenho honesto separa três grupos:

  • Leitura tolerante: dados que podem ser exibidos a partir de cache, como catálogo, histórico, agenda e detalhes de cliente.
  • Escrita enfileirada: ações que podem ser registradas localmente e enviadas depois, como comentários, formulários, evidências e check-ins.
  • Operação bloqueada: ações que exigem confirmação online, como pagamento, resgate de benefício, aprovação regulatória ou mudança irreversível.
Tela de app mobile sincronizando dados em rede instável
Tela de app mobile sincronizando dados em rede instável

Como modelar dados locais sem criar outro backend?

O erro comum é transformar o armazenamento local em uma cópia improvisada do backend. Isso complica migração, aumenta risco de divergência e torna cada release uma aposta. O app precisa de um modelo local menor, orientado às tarefas que ele realmente executa sem rede.

Em React Native, opções como SQLite, WatermelonDB, Realm e MMKV aparecem com frequência, mas elas resolvem problemas diferentes. MMKV é ótimo para chave-valor rápido, preferências e pequenos estados. SQLite funciona bem para dados relacionais, consultas e migrações explícitas. Realm e WatermelonDB podem ajudar em modelos reativos e volumes maiores, desde que o time aceite suas convenções.

Um contrato simples de entidade local já reduz muito ruído:

type LocalRecord<T> = {
  id: string;
  serverId?: string;
  data: T;
  syncStatus: 'synced' | 'pending' | 'failed' | 'conflict';
  updatedAt: string;
  version?: number;
};

Como sincronizar sem duplicar, perder ou sobrescrever dados?

Sincronização mobile é menos sobre velocidade e mais sobre idempotência. A mesma ação pode ser enviada duas vezes depois de uma queda de rede, o servidor pode responder com atraso e o usuário pode editar o mesmo item em dois dispositivos. Se a API não foi desenhada para isso, o app vira o culpado visível de um problema distribuído.

Para escritas enfileiradas, cada operação deve ter um identificador único gerado no cliente, como um UUID. O backend precisa aceitar esse idempotency key e devolver a mesma resposta quando receber a operação repetida. Isso evita duplicar pedidos, comentários, anexos ou lançamentos quando o usuário toca em tentar novamente.

Também é útil separar sincronização de upload pesado. Fotos, vídeos e PDFs devem ter estado próprio, com progresso, retomada e limite de tamanho. Um formulário pode estar salvo, mas com três anexos pendentes. A interface precisa mostrar essa diferença.

Conflitos exigem política explícita. Last write wins é simples, mas pode apagar informação válida. Merge por campo funciona em cadastros previsíveis. Revisão manual faz sentido quando há impacto operacional. Em qualquer caso, o app deve explicar o estado em linguagem direta: salvo neste dispositivo, aguardando envio, enviado, falhou ou precisa de revisão.

No React Native atual, em que a documentação lista a série 0.87 como versão mais recente, a New Architecture reduz custos de ponte em módulos nativos, mas não elimina decisões de domínio. Sincronização continua sendo um contrato entre app, API e produto. O código nativo ajuda em tarefas como background fetch, notificações, armazenamento seguro e upload resiliente; ele não corrige uma API que não entende repetição ou conflito.

O que muda no release e nos testes?

Offline-first aumenta a responsabilidade de testes porque muitos bugs só aparecem em transições: online para offline, app aberto para background, bateria baixa, upload interrompido, token expirado, banco migrado e versão antiga conversando com API nova. Testar apenas o caminho feliz com Wi-Fi é quase inútil.

Um conjunto mínimo de cenários deve incluir modo avião, rede lenta, troca de rede, encerramento forçado do app durante sincronização, atualização de versão com dados pendentes e conflito entre dois dispositivos. No Android, vale testar aparelhos intermediários e políticas agressivas de background. Em 2026, o Google Play passa a exigir target Android 16/API 36 para novos apps e atualizações a partir de 31 de agosto, o que reforça a necessidade de acompanhar permissões e comportamento de segundo plano por versão.

Do lado de release, feature flags ajudam, mas não bastam. O app deve registrar métricas por versão: quantidade de itens pendentes, tempo médio até sincronizar, taxa de falha por endpoint, tamanho da fila e número de conflitos. Crash-free sessions acima de 99,8% pode parecer bom, mas não diz se 7% dos usuários estão presos com uploads falhando há dois dias.

A melhor arquitetura mobile é a que preserva trabalho do usuário. React Native pode entregar interface e fluxo com velocidade, enquanto Swift, Kotlin e SDKs nativos cuidam das partes em que a plataforma manda. O ponto é assumir a rede como variável instável desde o início. Quando isso vira requisito técnico, o app para de depender da sorte.

Perguntas frequentes

O que é app mobile offline-first?

É um app desenhado para continuar útil quando a conexão falha. Ele salva dados localmente, mostra estados de sincronização e envia ações ao servidor quando a rede volta.

React Native funciona bem para apps offline?

Sim, desde que o projeto escolha armazenamento local adequado, modele fila de sincronização e use módulos nativos quando precisar de segurança, background ou uploads robustos. O risco não está no React Native em si, mas em tratar offline como detalhe tardio.

Qual banco local usar em React Native?

Depende do uso. MMKV serve para chave-valor e estados pequenos; SQLite é forte para dados estruturados e consultas; Realm e WatermelonDB ajudam em modelos reativos e bases maiores, mas exigem aceitar suas abstrações.

Como evitar duplicidade na sincronização mobile?

Use identificadores únicos por operação e suporte a idempotência no backend. Assim, se o app reenviar a mesma ação depois de uma queda de rede, o servidor reconhece a repetição e não cria registros duplicados.