Se o seu app trava, duplica dados ou perde atualizações quando a conexão cai, o usuário não perdoa. Ele desinstala. Este guia prático do appnoodles.com mostra como projetar um app móvel offline-first que sincroniza com segurança, resolve conflitos com previsibilidade, opera com filas de tarefas idempotentes, aplica backoff exponencial com jitter e protege tudo com criptografia local. Objetivo direto: reduzir suporte, aumentar retenção e ganhar velocidade de desenvolvimento sem virar refém de edge cases silenciosos.
Offline-first não é um “modo avião bonito”. É uma decisão de arquitetura: o app deve funcionar inteiro mesmo sem rede e reconciliar-se com o servidor quando puder. Isso exige modelo de dados sincronizável, trilha de mudanças e contratos de API que respeitem versões e conflitos. Pense como um mensageiro que anota cada carta enviada e recebida, com número de série e horário, para nunca perder o fio.
Sem identificadores estáveis, não há reconciliação. Gere UUIDs no cliente para novos registros, evite IDs sequenciais do servidor em entidades criadas offline e mantenha um log de operações (create/update/delete) com metadados: timestamp local, versão do registro conhecida pelo cliente, usuário e origem. Bancos locais como SQLite (via Room/Core Data), ou bancos orientados a documentos (ex.: Realm), funcionam bem. O ponto-chave não é a ferramenta, é a disciplina: persistir operações atômicas que podem ser reexecutadas sem efeitos colaterais.
Para que o servidor valide concorrência, envie junto de cada operação a versão conhecida do registro (ETag, hash ou número de versão). O servidor responde com “pré-condição falhou” se a versão mudou e aciona o fluxo de resolução de conflitos. Evite confiar no relógio do dispositivo para verdade absoluta; use carimbo de tempo apenas como sinal. Quando possível, prefira versionamento monotônico controlado pelo servidor.
Conflito não é bug, é sinal de que seu app é usado de verdade em ambientes imprevisíveis. Resolver bem significa definir estratégias por tipo de dado e nunca tratar tudo como “última escrita vence” por preguiça técnica.
Last-Write-Wins (LWW) é simples e rápido, mas pode apagar edições válidas. Útil para campos “menores” (ex.: favorito true/false). Merge por campo analisa cada atributo: para textos longos, preserve a versão mais recente, para contadores, some, para listas, faça união com deduplicação. CRDTs (tipos de dados replicados sem conflito) brilham em contadores, conjuntos e documentos colaborativos, mas aumentam a complexidade e o tamanho dos payloads. Minha prática: misto pragmático — CRDT onde a colaboração simultânea é requisito central; merges por campo no restante; LWW como fallback consciente.
O cliente envia a operação com a versão conhecida. Se o servidor detectar divergência, retorna o estado atual e metadados de conflito. O cliente então:
Quando o merge não é determinístico (ex.: corpo de texto editado por duas pessoas), apresente uma UI de conflito clara, com histórico e opção de escolher partes. É melhor pedir uma decisão informada do que “resolver” e decepcionar silenciosamente.
Dica de ouro: documente regras de merge por entidade no mesmo repositório do código. Testes automatizados de conflito evitam regressões caras e discutidas somente quando o app já está na mão do usuário.
O coração do offline-first é a fila de tarefas. Toda ação que impacta o servidor gera uma tarefa persistida localmente. A fila executa, confirma, reprograma ou marca como falha definitiva, sem nunca bloquear a UI.
Idempotência evita estragos quando a mesma operação é reenviada. Use requestId único por tarefa e garanta que o servidor trate duplicatas como confirmações. Estruture a tarefa com: tipo, payload serializado, dependências (ex.: criar antes de atualizar), política de tentativa, estado (pendente, em progresso, concluída, falhou), e checkpoint para retomar do ponto certo após crash.
Sem controle, você transforma seu app em um DDoS portátil. Implemente backoff exponencial (ex.: 1s, 2s, 4s, 8s…) com jitter aleatório para desalinhar enxames de clientes. Após X falhas correlatas (timeouts, 5xx), abra um circuit breaker: pause a fila por um intervalo maior, exiba um estado informativo ao usuário e evite drenagem de bateria.
Erros 4xx devem falhar rápido com mensagem inteligível. Erros transitórios (rede, 5xx) entram na política de reintento. Em redes móveis, limite a concorrência da fila (ex.: 2 a 3 tarefas) e priorize tarefas pequenas para dar sensação de progresso.
Insight prático: trate upload de mídia como “tarefa composta”: primeiro suba o arquivo (com recomeço por partes), depois registre o metadado. Se a segunda etapa falhar, não reenvie a mídia gigante à toa.
Se há dados sensíveis, criptografe em repouso. Isso protege contra acesso físico ao dispositivo e ferramentas de extração forense básica. Entretanto, segurança sem UX não se sustenta; busque equilíbrio entre proteção e fluidez.
Armazene a chave mestra em hardware-backed keystore quando disponível. Desbloqueie o banco local apenas quando necessário, cacheie de forma segura durante a sessão e re-bloqueie ao sair ou após inatividade. Se fizer sentido, ofereça biometria como camada de conveniência. Evite derivar chaves diretamente de senhas fracas; use derivação robusta e sal adequada.
Criptografia impacta latência e consumo de bateria, e pode complicar migrações de esquema. Planeje rotinas de rotação de chave e migração incremental. Em cenários com notificações push que incluem dados sensíveis, criptografe o conteúdo e desenhe o fluxo para decriptar somente quando o app estiver apto, sem vazar metadados desnecessários.
O que não é medido vira lenda urbana no suporte. Colete métricas de tempo médio de sincronização, taxa de conflitos, tamanho médio de payload, bytes por sessão e falhas por tipo. Registre eventos da fila (enfileirada, reintentada, concluída) com correlação por requestId. Em privacidade, minimize PII e aplique anonimização.
Teste em condições reais: 2G, perda intermitente, proxy que injeta latência, trocas rápidas de Wi‑Fi para 4G/5G. Simule relógio do sistema adiantado/atrasado e faltas de energia. Automação ajuda, mas walk tests no mundo físico revelam gargalos que o simulador não mostra.
Aprendizado de campo: sempre que possível, prefira sincronização por “delta de mudanças” em vez de baixar coleções inteiras. Reduz custos, acelera a percepção de desempenho e diminui a superfície de conflito.
Usuário cria nota offline com três fotos. O app gera UUIDs locais para a nota e para cada foto, grava a nota no banco criptografado e enfileira quatro tarefas: upload das três mídias (com recomeço por partes) e, por fim, criação da nota com referências aos mediaIds retornados. Em 4G congestionado, o backoff segura picos, a fila mantém duas tarefas concorrentes e o circuito abre após cinco 5xx. Ao recuperar a rede, as tarefas retomam do último chunk enviado. Se a nota foi editada em outro dispositivo, o servidor detecta versão divergente, devolve o estado e o cliente aplica merge por campo,