Pular para o conteúdo
B
Bradata
mobileoffline-firstfrotas

App offline-first para frota: SQLite, sync conflict-free e bateria — o blueprint completo

Guia técnico denso para construir app mobile offline-first robusto: arquitetura SQLite, sync incremental sem conflito, gestão de bateria com GPS contínuo e patterns de produção em frotas brasileiras.

Por Bradata··9 min de leitura

Onde a internet acaba começa o app real

App de gestão de frota brasileiro tem que funcionar em estrada do interior do MA com 1 barra de sinal 2G. Em caminhão na cabine subterrânea de um centro de distribuição. Em balsa no Amazonas sem sinal por 3 horas. App online-only é app inútil em frota brasileira.

A regra: toda funcionalidade crítica funciona offline. Sincronização rola quando a conexão volta. O motorista nunca vê erro "sem internet" no meio de uma entrega.

Esse post é o blueprint técnico do app offline-first para frotas — SQLite, estratégia de sync conflict-free, gestão de bateria com GPS contínuo, patterns que funcionam em produção real. Aprofunde também em Gestão de frotas com IoT: rastreamento GPS e manutenção preditiva e React Native vs Flutter vs nativo em 2026.

A arquitetura geral

[ APP (React Native + TypeScript) ]
   ↓
[ Banco local: SQLite ]
   - Tabelas mirror do banco remoto
   - Coluna `_status` (synced, pending_create, pending_update, pending_delete)
   - Coluna `_last_modified_at` (timestamp lógico)
   - Coluna `_remote_id` (UUID gerado offline)

[ Engine de sincronização ]
   - Detecta conexão restaurada
   - Empurra alterações locais
   - Puxa alterações remotas
   - Resolve conflito (LWW ou estratégia específica)

[ Servidor (Node.js/Python) ]
   - Endpoint /sync
   - Versionamento por entidade
   - Histórico de mudanças

[ Banco remoto: PostgreSQL ]
   - Append-only events log (auditoria)
   - Tabelas materializadas

A escolha de banco local: SQLite com qual abstração

Três caminhos comuns em React Native:

1. react-native-sqlite-storage (raw)

SQL puro. Total controle, sem mágica. Bom para apps com poucas entidades.

2. WatermelonDB

ORM reativo construído sobre SQLite. Modela tabelas como classes, relações declarativas, observables reativos (a UI atualiza quando o dado muda no banco). Build especial otimizado.

// Schema
import { Model } from '@nozbe/watermelondb'
import { field, date, readonly } from '@nozbe/watermelondb/decorators'

class Delivery extends Model {
  static table = 'deliveries'
  @field('cliente_nome') clienteNome!: string
  @field('endereco') endereco!: string
  @field('status') status!: 'pendente' | 'em_rota' | 'entregue' | 'falha'
  @date('data_planejada') dataPlanejada!: Date
  @readonly @date('created_at') createdAt!: Date
  @readonly @date('updated_at') updatedAt!: Date
}

WatermelonDB é o que a Bradata usa em apps de frota — escala bem (testado com 50k+ registros locais), tem sync engine pronto pra estender, e UI reativa torna ergonomia de dev imensa.

3. Realm Mobile (MongoDB)

ORM próprio com sync nativo via MongoDB Atlas Device Sync. Vantagem: sync gerenciado. Desvantagem: vendor lock-in MongoDB.

4. PowerSync

Plataforma de sync sobre Postgres direto pro cliente. Mais nova, promissora, mas ainda em maturação.

Recomendação Bradata 2026: WatermelonDB para projetos novos React Native, especialmente quando o backend é Postgres customizado (nossa stack default).

A estratégia de sincronização

Sync é a parte mais difícil de qualquer app offline-first. Erros comuns: data corruption, conflito mal resolvido, sync infinito, bateria sumindo. Os patterns que funcionam:

Padrão 1 — Pull then Push

Sequência clássica:

  1. Pull — recebe do servidor o que mudou desde último sync (since=timestamp)
  2. Merge — aplica essas mudanças localmente, resolvendo conflitos
  3. Push — envia para o servidor o que mudou localmente (com status pending_*)
  4. Acknowledge — marca local como synced após confirmação
async function sync() {
  const lastSyncAt = await getLastSyncTimestamp()
  
  // Pull
  const remoteChanges = await api.get(`/sync?since=${lastSyncAt}`)
  await applyRemoteChanges(remoteChanges)
  
  // Push
  const localChanges = await getLocalChangesByStatus(['pending_create', 'pending_update', 'pending_delete'])
  if (localChanges.length > 0) {
    const result = await api.post('/sync', { changes: localChanges })
    await markAsSynced(result.acknowledged)
  }
  
  await setLastSyncTimestamp(Date.now())
}

Padrão 2 — Logical Clock (Lamport)

Em vez de timestamp wall-clock (que difere entre dispositivos), use clock lógico. Cada update incrementa um contador. Servidor mantém ordem total.

Util quando temos clock skew (dispositivo Android com hora errada).

Padrão 3 — Resolução de conflito por tipo

Quando o mesmo registro foi editado offline em duas pontas (motorista + dispatcher web, por exemplo):

Tipo de campoEstratégia
Status enumerado (pendente → em_rota → entregue)Avança no fluxo. "entregue" ganha sobre "em_rota".
Texto observacionalLast-Write-Wins ou concatenação.
Coordenada GPS (timestamp + posição)Mais recente vence.
Lista (ex: itens da entrega)Merge com chave (id do item).
Contador numérico (ex: km percorrido)Soma de deltas.

LWW (Last-Write-Wins) puro mata dados em alguns cenários. Aplicação séria define estratégia por entidade.

Padrão 4 — CRDT para colaboração concorrente

CRDT (Conflict-free Replicated Data Type) é estrutura matemática que garante convergência. Tipos:

  • G-Counter — contador que só cresce, soma de réplicas
  • PN-Counter — contador que cresce e decresce
  • OR-Set — set com adds e removes idempotentes
  • LWW-Set — set com timestamp

Bibliotecas: Yjs, Automerge.

Para frota, CRDT é overkill na maioria dos casos — não há colaboração simultânea no mesmo registro. LWW + resolução por tipo basta. CRDT entra em casos como app colaborativo (Notion-like).

Gestão de bateria com GPS contínuo

App de motorista precisa rastrear posição o dia inteiro. GPS contínuo a 1Hz consome ~12% da bateria por hora em iPhone padrão. Em Android mid-range, 18–22%. Para turno de 8h, bateria seca em meio expediente.

Estratégias para reduzir o consumo:

1. GPS por movimento, não por tempo

Em vez de "obtenha posição a cada 30s", use listener de mudança significativa de localização (LocationManager Android, CLLocationManager iOS). Notifica só quando o veículo se moveu X metros.

Em React Native: react-native-background-geolocation (paga, robusta) ou react-native-background-fetch + Geolocation API.

2. Activity Recognition

Sistemas modernos têm API de reconhecimento de atividade (em parado, andando, dirigindo). Quando reconhece "parado", reduz frequência de GPS drasticamente. Quando "dirigindo", aumenta.

Reduz consumo em 50–70% em rotas com paradas longas.

3. Geofences

Definir geofences (raios de N metros em torno de pontos de interesse — depósito, cliente, parada). Quando entra/sai do geofence, evento dispara — sem precisar GPS contínuo "esperando".

Para entrega, geofence ao redor do endereço do cliente = chegada detectada automaticamente.

4. Foreground service no Android

Android mata serviços em background agressivamente. Para garantir GPS rodando sem ser morto, usar Foreground Service com notificação persistente. Usuário vê "App rastreando posição" — transparente.

iOS exige modo Always Permission + Background Modes: Location updates. Apple aprova só com justificativa clara no submit da App Store.

5. Batch upload

Coleta posições localmente, envia em batch a cada 60s (ou quando aparece WiFi/4G estável). Cada upload tem custo de bateria (rádio) — agrupar economiza.

6. Adaptação dinâmica

App detecta nível de bateria. Abaixo de 20%, reduz frequência ainda mais. Abaixo de 10%, modo emergência: só geofence + posições críticas (chegada/saída).

Patterns de UI para offline

App offline-first tem padrões de UI específicos:

Indicador de sync status

Sempre mostrar:

  • 🟢 Sincronizado
  • 🟡 Sincronizando agora
  • 🔴 Sem conexão, dados pendentes
  • ⚠️ Erro de sync (clicável pra detalhes)

Usuário precisa saber se a ação dele está chegando ao servidor ou não.

Operações "fire and forget"

Quando motorista clica "marcar entregue", a UI muda imediatamente (estado local muda). Não fica spinning esperando server. Sync acontece em background.

Se sync falhar 5 vezes, item vai para "exceções" com botão "tentar novamente manualmente".

Conflito de dados (raro, mas existe)

Se motorista marcou entrega como "concluída" mas backend tem ela como "cancelada" (foi cancelada via portal web enquanto offline), mostrar tela de resolução:

  • "Sua versão: concluída às 14:32"
  • "Versão servidor: cancelada às 13:45 pelo João"
  • "Qual versão prevalece?"

Decisão é do usuário com mais autoridade.

Cache de mapas offline

Mapa precisa funcionar offline. Soluções:

  • Mapbox Offline (paga, ergonômica)
  • MapLibre + OSM tiles (open source, requer setup de tile server)
  • MaplY/Vector tiles pré-baixados por região operacional

Cache só de regiões que o motorista vai operar (não 4GB de mapa do Brasil inteiro).

Sincronização específica para frota: o que precisa estar offline

FuncionalidadeOffline?Estratégia
LoginApós primeiro login, simToken + biometria local
Ver rota do diaSimBaixa ao logar de manhã
Listar entregasSimLocal
Detalhar entrega (endereço, contato)SimLocal
Tirar foto (canhoto, RG, danos)SimLocal, sync depois
Assinatura digitalSimLocal, sync depois
Marcar entregue/falhaSimEstado local imediato
Registrar ocorrênciaSimLocal, sync depois
Chat com despachanteNão puro: enfileira mensagem, sync quando rede volta
Atualizar rota dinamicamenteNão: precisa conexão para receber novidade
Pagamento (cobrança no destino)Híbrido: aceita comprovante offline, valida online depois

Resolução de problemas em produção

"App não sincroniza há 3 dias"

  • Verificar último timestamp de sync no log
  • Verificar se token expirou (60 dias inativo)
  • Verificar tamanho do delta acumulado (se > 5MB, partição em batches menores)
  • Verificar throttling do servidor (excesso de calls)

"Bateria sumiu em 4 horas"

  • Verificar GPS frequency setting
  • Verificar Foreground Service notification persistente (alguma OEM mata mesmo assim)
  • Verificar WakeLock excessivo
  • Em Xiaomi/OnePlus/Huawei: orientar usuário a colocar app em whitelist de autostart

"App crasha ao abrir lista de 5000 entregas"

  • FlatList básica do RN sofre — usar FlashList (Shopify) com estimatedItemSize
  • Paginação local (não renderizar todos de uma vez)
  • Virtualização agressiva

"Banco SQLite ficou corrupto"

  • Habilitar WAL mode (Write-Ahead Logging) — mais robusto a crash
  • Backup local periódico do .db
  • Em última instância, drop + sync from scratch (UX ruim, mas recupera)

A entrega que a Bradata faz

Para clientes em frota e logística, construímos app mobile que tem:

  • React Native + TypeScript + WatermelonDB
  • Sync incremental com Postgres backend
  • GPS background otimizado (Activity Recognition + geofences)
  • Foreground Service Android para garantir rastreio
  • Cache de mapas Mapbox por região
  • Comprovante digital (foto + assinatura + geolocalização)
  • Modo offline robusto — vendido com SLA de "0 falha de venda por internet"
  • Bateria-friendly — testado em turno de 10h

Veja na prática no nosso software de gestão de frotas e em Gestão de frotas com IoT. Aprofunde também em Desenvolvimento de App Mobile Corporativo.

Conclusão

App offline-first não é "online com cache". É arquitetura inteira diferente — desde modelo de dados até gestão de estado, sincronização e UX. Apps que tentam "adicionar offline" depois falham. Apps construídos offline-first desde o dia 1 funcionam em produção pesada.

Para frotas brasileiras com operação em qualquer região do país, offline-first é requisito. App online-only é falha de produto.

Se você está construindo app de frota, logística ou qualquer operação de campo, fale conosco. A Bradata desenvolve apps mobile sob medida — conheça Desenvolvimento de App Mobile Corporativo e veja nossos cases.

A Bradata é uma software house brasileira com 35+ apps em produção. Veja soluções e nossas verticais.


Fontes: WatermelonDB Documentation v0.27+, React Native Background Geolocation Docs, Apple Core Location WWDC 2024, Android Activity Recognition API, Realm/MongoDB Sync Best Practices 2025, projetos internos Bradata.

Precisa de um talento tech agora?

Fale com a Bradata e receba uma proposta em 24 horas úteis.