SQLite vs Realm para armazenamento offline-first em apps de campo
SQLite vs Realm comparados para armazenamento offline-first em apps de campo: migrações, opções de consulta, resolução de conflitos, ferramentas de depuração e dicas práticas para escolher.

O que apps de campo offline-first realmente precisam
Offline-first não significa apenas “funciona sem internet”. Significa que o app carrega dados úteis, aceita novas entradas e mantém cada edição segura até que possa sincronizar.
Trabalho de campo traz um conjunto previsível de restrições: sinal que cai e volta, sessões longas, aparelhos antigos e modos de economia de bateria comuns. As pessoas se movem rápido. Abrem um job, rolam listas longas, tiram fotos, preenchem formulários e já partem para a próxima tarefa sem pensar no armazenamento.
O que os usuários notam é simples. Eles perdem confiança quando edições desaparecem, quando listas e buscas ficam lentas offline, quando o app não responde claramente “meu trabalho foi salvo?”, quando registros duplicam ou somem após reconectar, ou quando uma atualização causa comportamento estranho.
Por isso escolher entre SQLite e Realm é mais sobre comportamento do dia a dia que sobre benchmarks.
Antes de escolher um banco local, fique claro em quatro áreas: seu modelo de dados vai mudar, suas consultas devem corresponder aos fluxos reais, a sincronização offline gerará conflitos, e as ferramentas decidirão quão rápido você diagnostica problemas em campo.
1) Seus dados vão mudar
Mesmo apps estáveis evoluem: campos novos, status renomeados, telas novas. Se mudanças de modelo forem dolorosas, você ou entrega menos melhorias ou corre o risco de quebrar dispositivos reais com dados reais.
2) Consultas devem bater com fluxos reais
Apps de campo precisam de filtros rápidos como “jobs de hoje”, “sites próximos”, “formulários não sincronizados” e “itens editados nas últimas 2 horas”. Se o banco torna essas consultas complicadas, a UI fica lenta ou o código vira um labirinto.
3) Sincronização offline cria conflitos
Duas pessoas podem editar o mesmo registro, ou um aparelho pode editar dados antigos por dias. Você precisa de um plano claro sobre o que vence, o que se mescla e o que precisa de decisão humana.
4) Ferramentas importam
Quando algo dá errado em campo, você precisa inspecionar dados, reproduzir problemas e entender o que aconteceu sem adivinhação.
Migrações: mudar o modelo sem quebrar usuários
Apps de campo raramente ficam parados. Após algumas semanas você adiciona uma checkbox, renomeia um status ou divide um campo “notas” em campos estruturados. Migrações são onde apps offline frequentemente falham, porque o telefone já tem dados reais.
SQLite armazena dados em tabelas e colunas. Realm armazena como objetos com propriedades. Essa diferença aparece rápido:
- No SQLite, normalmente você escreve mudanças explícitas no esquema (ALTER TABLE, tabelas novas, cópia de dados).
- No Realm, costuma-se aumentar a versão do esquema e rodar uma função de migração que atualiza objetos conforme eles são acessados.
Adicionar um campo é fácil em ambos: coluna nova no SQLite, propriedade com default no Realm. Renomear e dividir é o que dói. No SQLite, renomear pode ser limitado dependendo do seu setup, então equipes muitas vezes criam uma tabela nova e copiam dados. No Realm, você pode ler a propriedade antiga e escrever nas novas durante a migração, mas precisa ter cuidado com tipos, defaults e nulls.
Atualizações grandes em aparelhos com dados exigem cautela. Uma migração que reescreve todo registro pode ser lenta em telefones antigos, e um técnico não deveria ficar preso olhando um spinner num estacionamento. Planeje tempo de migração e considere espalhar transformações pesadas por várias releases.
Para testar migrações com justiça, trate-as como sincronização:
- Instale uma build antiga, crie dados realistas e depois atualize.
- Teste datasets pequenos e grandes.
- Mate o app no meio da migração e reabra.
- Teste cenários de pouco armazenamento.
- Assuma que você pode avançar a migração mesmo que não consiga voltar atrás.
Exemplo: se “equipmentId” vira “assetId” e depois se divide em “assetType” mais “assetNumber”, a migração deve manter inspeções antigas usáveis, não forçar logout ou wipe.
Flexibilidade de consulta: o que você pode pedir dos seus dados
Apps de campo vivem ou morrem em telas de lista: jobs de hoje, ativos próximos, clientes com chamados abertos, peças usadas esta semana. Sua escolha de armazenamento deve tornar essas perguntas fáceis de expressar, rápidas de rodar e difíceis de serem mal interpretadas seis meses depois.
SQLite te dá SQL, que ainda é a forma mais flexível de filtrar e ordenar grandes datasets. Você pode combinar condições, juntar tabelas, agrupar resultados e adicionar índices quando uma tela fica lenta. Se o app precisa de “todas as inspeções para ativos na Região A, atribuídas à Equipe 3, com qualquer item de checklist reprovado”, o SQL costuma expressar isso de forma clara.
Realm aposta em objetos e em uma API de consulta de nível mais alto. Para muitos apps isso parece natural: consultar objetos Job, filtrar por status, ordenar por data de vencimento, seguir relações para objetos relacionados. A troca é que algumas perguntas triviais em SQL (especialmente consultas do tipo relatório através de várias relações) podem ser mais difíceis no Realm, ou você acaba remodelando dados para casar com as consultas que precisa.
Busca e relacionamentos
Para busca de texto parcial em vários campos (título do job, nome do cliente, endereço), o SQLite costuma empurrar para indexação cuidadosa ou uma abordagem de full-text dedicada. Realm também pode filtrar texto, mas você precisa pensar em performance e no que “contém” significa em escala.
Relacionamentos são outro ponto prático. SQLite lida com one-to-many e many-to-many com tabelas de junção, o que torna padrões como “ativos com essas duas tags” diretos. Links do Realm são fáceis de navegar no código, mas muitos-para-muitos e padrões de “query through” geralmente precisam de mais planejamento para manter leituras rápidas.
Queries brutas vs manutenção legível
Um padrão que ajuda manutenção é manter um conjunto pequeno de consultas nomeadas que mapeiam diretamente para telas e relatórios: filtros e ordenações da lista principal, a query da vista de detalhe (um registro mais registros relacionados), a definição de busca, alguns contadores (badges e totais offline) e quaisquer consultas de exportação/relatório.
Se você espera perguntas ad hoc frequentes do negócio, o poder das queries brutas do SQLite é difícil de bater. Se você quer que o acesso a dados pareça com trabalhar com objetos normais, o Realm pode ser mais rápido para construir, desde que responda às suas telas mais complexas sem macetes estranhos.
Resolução de conflitos e sincronização: que suporte você obtém
Apps offline-first de campo normalmente suportam as mesmas ações básicas desconectadas: criar, atualizar e apagar registros inválidos. A parte difícil não é salvar localmente. É decidir o que acontece quando dois dispositivos mudam o mesmo registro antes de qualquer um sincronizar.
Conflitos aparecem em situações simples. Um técnico atualiza uma inspeção num tablet num porão sem sinal. Depois, um supervisor corrige a mesma inspeção num laptop. Quando ambos reconectam, o servidor recebe duas versões diferentes.
A maioria das equipes adota uma destas abordagens:
- Last write wins (rápido, mas pode sobrescrever dados bons sem aviso)
- Merge por campo (mais seguro quando campos diferentes mudam, mas precisa de regras claras)
- Fila de revisão manual (mais lento, melhor para mudanças de alto risco)
SQLite te dá um banco local confiável, mas não oferece sincronização por si só. Você normalmente constrói o resto: rastrear operações pendentes, enviá-las para uma API, tentar novamente com segurança e aplicar regras de conflito no servidor.
O Realm pode reduzir a quantidade de boilerplate se você usar seus recursos de sync, porque foi projetado em torno de objetos e tracking de mudanças. Mas “sync embutido” ainda não escolhe suas regras de negócio. Você decide o que conta como conflito e qual dado deve vencer.
Planeje um rastro de auditoria desde o começo. Equipes de campo muitas vezes precisam responder claramente “quem mudou o quê, quando e de qual dispositivo”. Mesmo que você escolha last write wins, armazene metadata como user ID, device ID, timestamps e (quando possível) um motivo. Se seu backend for gerado rapidamente, por exemplo com uma plataforma no-code como AppMaster, fica mais fácil iterar nessas regras cedo antes de ter centenas de dispositivos offline em produção.
Depuração e inspeção: pegar problemas antes que o campo veja
Bugs offline são difíceis porque acontecem quando você não pode assistir o app falando com um servidor. Sua experiência de depuração costuma se resumir a uma pergunta: com que facilidade você pode ver o que está no dispositivo e como isso mudou ao longo do tempo?
SQLite é fácil de inspecionar porque é um arquivo. Em dev ou QA você pode puxar o banco de um dispositivo de teste, abrir com ferramentas comuns de SQLite, rodar queries ad hoc e exportar tabelas para CSV ou JSON. Isso ajuda a confirmar “quais linhas existem” versus “o que a UI mostra”. A desvantagem é que você precisa entender seu esquema, joins e qualquer scaffolding de migração que criou.
Realm pode parecer mais “do tipo app” para inspecionar. Os dados são armazenados como objetos, e as ferramentas do Realm costumam ser a forma mais fácil de navegar classes, propriedades e relacionamentos. É ótimo para notar problemas em grafos de objetos (links faltando, nulls inesperados), mas análise ad hoc é menos flexível se sua equipe está acostumada com inspeção baseada em SQL.
Logging e reproduzir problemas offline
A maioria das falhas em campo se resume a erros silenciosos de gravação, lotes de sync parciais ou uma migração que só terminou pela metade. De qualquer forma, invista em alguns básicos: timestamps “última alteração” por registro, um log de operações no dispositivo, logs estruturados sobre migrações e gravações em background, uma forma de ativar logs verbosos em builds de QA e uma ação “dump and share” que exporte um snapshot com dados sensíveis redigidos.
Exemplo: um técnico relata que inspeções concluídas somem após a bateria descarregar. Um snapshot compartilhado ajuda a confirmar se os registros nunca foram gravados, foram gravados mas não consultados, ou foram revertidos na inicialização.
Compartilhar um snapshot com falha
Com SQLite, compartilhar costuma ser tão simples quanto enviar o arquivo .db (mais arquivos WAL, se houver). Com Realm, normalmente você compartilha o arquivo Realm junto com arquivos auxiliares. Em ambos os casos, defina um processo repetível para remover dados sensíveis antes de qualquer coisa sair do dispositivo.
Confiabilidade no mundo real: falhas, resets e upgrades
Apps de campo falham de maneiras mundanas: bateria acaba no meio do salvamento, o SO mata o app em background, ou o armazenamento enche após semanas de fotos e logs. Sua escolha de banco local afeta com que frequência essas falhas viram trabalho perdido.
Quando um crash acontece no meio da gravação, tanto SQLite quanto Realm podem ser seguros quando usados corretamente. SQLite é confiável quando você envolve mudanças em transações (modo WAL ajuda resiliência e performance). Gravações do Realm são transacionais por padrão, então normalmente você tem salvamentos “tudo ou nada” sem trabalho extra. O risco comum não é o mecanismo do banco, é o código do app que escreve em múltiplas etapas sem ponto claro de commit.
Corrupção é rara, mas você precisa de um plano de recuperação. Com SQLite você pode rodar checagens de integridade, restaurar de backup conhecido ou reconstruir a partir de uma ressincronização com o servidor. Com Realm, corrupção frequentemente coloca em dúvida todo o arquivo Realm, então o caminho prático costuma ser “apagar o local e ressincronizar” (ok se o servidor for fonte da verdade, doloroso se o dispositivo tiver dados únicos).
Crescimento do armazenamento é outra surpresa. SQLite pode inchar após deletes a menos que você rode vacuum periodicamente. Realm também pode crescer e pode precisar de políticas de compactação, além de podar objetos antigos (como jobs concluídos) para o arquivo não expandir indefinidamente.
Upgrades e rollbacks são outra armadilha. Se uma atualização muda o esquema ou formato de armazenamento, um rollback pode deixar usuários presos num arquivo mais novo que não dá para ler. Planeje upgrades como unidirecionais, com migrações seguras e uma opção de “reset de dados locais” que não quebre o app.
Hábitos de confiabilidade que valem a pena:
- Trate “disco cheio” e falhas de escrita com uma mensagem clara e caminho de retry.
- Salve entrada do usuário em checkpoints, não só no final de um formulário longo.
- Mantenha um log de auditoria leve para recuperação e suporte.
- Prune e arquive registros antigos antes que o banco cresça demais.
- Teste upgrades de SO e kills em background em dispositivos de baixo custo.
Exemplo: um app de inspeção que guarda checklists e fotos pode atingir pouco espaço em um mês. Se o app detectar pouco espaço cedo, pode pausar captura de fotos, subir quando possível e manter saves de checklist seguros, independentemente do banco local escolhido.
Passo a passo: como escolher e configurar seu armazenamento
Trate armazenamento como parte do produto, não só como decisão de biblioteca. A melhor opção é a que mantém o app usável quando o sinal cai e previsível quando volta.
Um caminho simples de decisão
Escreva primeiro seus fluxos offline. Seja específico: “abrir jobs de hoje, adicionar notas, anexar fotos, marcar como concluído, capturar assinatura.” Tudo nessa lista deve funcionar sem rede, sempre.
Depois percorra uma sequência curta: liste telas críticas offline e quanto dado cada uma precisa (jobs de hoje vs histórico completo), esboce um modelo de dados mínimo e os relacionamentos que você não pode simular (Job -> ChecklistItems -> Answers), escolha uma regra de conflito por entidade (não uma só regra para tudo), decida como testar falhas (migrações em dispositivos reais, retries de sync, logout/reinstalação forçados), e construa um protótipo pequeno com dados realistas que você possa cronometrar (carregar, buscar, salvar, sincronizar após um dia offline).
Esse processo geralmente revela a real restrição: você precisa de consultas ad hoc flexíveis e inspeção fácil, ou valoriza acesso baseado em objetos e garantia de modelo?
O que validar no protótipo
Use um cenário realista, como um técnico que completa 30 inspeções offline e depois dirige de volta à cobertura. Meça tempo de primeiro carregamento com 5.000 registros, se uma alteração de esquema sobrevive a uma atualização, quantos conflitos aparecem após reconectar e se você consegue explicar cada um, e quão rápido consegue inspecionar um “registro ruim” quando o suporte liga.
Se quer validar fluxos rápido antes de se comprometer, um protótipo no-code em AppMaster pode ajudar a travar o fluxo e o modelo de dados cedo, mesmo antes de finalizar o banco no dispositivo.
Erros comuns que prejudicam apps offline-first
A maioria das falhas offline não vem do motor do banco. Vem de pular as partes chatas: upgrades, regras de conflito e tratamento claro de erros.
Uma armadilha é assumir que conflitos são raros. Em trabalho de campo eles são normais: dois técnicos editam o mesmo ativo, ou um supervisor muda um checklist enquanto um dispositivo está offline. Se você não define uma regra (last write wins, merge por campo, ou manter ambas versões), eventualmente vai sobrescrever trabalho real.
Outro fracasso silencioso é tratar o modelo de dados como “pronto” e não praticar upgrades. Mudanças de esquema acontecem até em apps pequenos. Se você não versiona esquema e não testa upgrades de versões antigas, usuários podem ficar presos após uma atualização com migrações falhas ou telas em branco.
Problemas de performance também aparecem tarde. Equipes às vezes baixam tudo “por precaução” e depois se perguntam por que a busca fica lenta e o app demora minutos para abrir num telefone intermediário.
Padrões para ficar de olho:
- Sem política de conflitos escrita, então edições são sobrescritas silenciosamente.
- Migrações que funcionam em instalações novas, mas falham em upgrades reais.
- Cache offline que cresce sem limites, deixando queries lentas.
- Falhas de sync escondidas atrás de um spinner, fazendo usuários suporem que os dados foram enviados.
- Depuração por tentativa e erro em vez de um roteiro de repro repetível e dados de exemplo.
Exemplo: um técnico completa uma inspeção offline, toca em Sync e não recebe confirmação. O upload falhou devido a token de auth. Se o app esconde o erro, ele sai do local achando que o job foi concluído, e a confiança some.
Qualquer que seja o armazenamento escolhido, rode um teste básico “modo campo”: modo avião, bateria baixa, atualização do app e dois dispositivos editando o mesmo registro. Se estiver construindo rápido com uma plataforma no-code como AppMaster, incorpore esses testes no protótipo antes que o fluxo chegue a uma equipe maior.
Checklist rápido antes de se comprometer
Antes de escolher um motor, defina o que “bom” significa para seu app de campo e teste com dados e dispositivos reais. Equipes discutem recursos, mas a maioria das falhas vem do básico: upgrades, telas lentas, regras de conflito pouco claras e sem forma de inspecionar estado local.
Use como critério de go/no-go:
- Prove upgrades: pegue pelo menos duas builds antigas, atualize para a build atual e confirme que os dados ainda abrem, editam e sincronizam.
- Mantenha telas principais rápidas com volume real: carregue dados realistas e cronometre as telas mais lentas num telefone intermediário.
- Escreva política de conflito por tipo de registro: inspeções, assinaturas, peças usadas, comentários.
- Torne dados locais inspecionáveis e logs coletáveis: defina como suporte e QA capturam estado offline.
- Faça recuperação previsível: decida quando reconstruir cache, rebaixar ou exigir novo login. Não faça “reinstalar o app” o plano.
Se estiver prototipando em AppMaster, aplique a mesma disciplina. Teste upgrades, defina conflitos e ensaie a recuperação antes de entregar a uma equipe que não pode pagar downtime.
Cenário exemplo: um app de inspeção com sinal instável
Um técnico baixa 50 ordens de trabalho no início do dia para o telefone. Cada job inclui endereço, itens de checklist exigidos e algumas fotos de referência. Depois disso, o sinal oscila o dia todo.
Durante cada visita, o técnico edita os mesmos registros repetidamente: status do job (Arrived, In Progress, Done), peças usadas, assinatura do cliente e novas fotos. Algumas edições são pequenas e frequentes (status). Outras são grandes (fotos) e não podem se perder.
O momento da sincronização: duas pessoas mexeram no mesmo job
Às 11:10, o técnico marca o Job #18 como Done e adiciona uma assinatura enquanto está offline. Às 11:40, um despachante reatribui o Job #18 porque ainda parece aberto no escritório. Quando o técnico reconecta às 12:05, o app envia as mudanças.
Um bom fluxo de conflito não esconde isso. Ele mostra. Um supervisor deve ver uma mensagem simples: “Existem duas versões do Job #18”, com campos chave lado a lado (status, técnico atribuído, timestamp, assinatura sim/não) e opções claras: manter atualização do campo, manter atualização do escritório ou mesclar por campo.
É aqui que suas decisões de armazenamento e sync aparecem na prática: você consegue rastrear um histórico limpo de mudanças e reproduzi-las com segurança após horas offline?
Quando um job “desaparece”, depurar geralmente é provar o que aconteceu. Logue o suficiente para responder: mapeamento de ID local para ID do servidor (incluindo quando foi criado), cada gravação com timestamp/usuário/dispositivo, tentativas de sync e mensagens de erro, decisões de conflito e o vencedor, e status de upload de fotos rastreado separadamente do registro do job.
Com esses logs, você pode reproduzir o problema em vez de adivinhar a partir de uma reclamação.
Próximos passos: valide rápido, depois construa a solução completa
Antes de se posicionar no debate SQLite vs Realm, escreva uma especificação de uma página para seus fluxos offline: as telas que um técnico vê, quais dados vivem no dispositivo e o que deve funcionar sem sinal (criar, editar, fotos, assinaturas, uploads enfileirados).
Depois prototipe o sistema inteiro cedo, não só o banco. Apps de campo falham nas junções: um formulário móvel que salva local não ajuda se a equipe admin não consegue revisar e corrigir registros, ou se o backend rejeita updates depois.
Um plano prático de validação:
- Construa uma fatia end-to-end fina: um formulário offline, uma vista de lista, uma tentativa de sync e uma tela admin.
- Rode um teste de mudança: renomeie um campo, divida um campo em dois, publique uma build de teste e veja como o upgrade se comporta.
- Simule conflitos: edite o mesmo registro em dois dispositivos, sincronize em ordens diferentes e veja o que quebra.
- Treine depuração em campo: decida como irá inspecionar dados locais, logs e payloads de sync falhos em um dispositivo real.
- Escreva uma política de reset: quando apagar cache local e como usuários recuperam sem perder trabalho.
Se velocidade importa, um primeiro passo no-code pode ajudar a validar o fluxo rápido. AppMaster (appmaster.io) é uma opção para construir a solução completa (serviços backend, painel admin web e apps móveis) cedo, e depois regenerar código limpo conforme os requisitos mudam.
Escolha o próximo passo com base no risco. Se formulários mudam semanalmente, teste migrações primeiro. Se várias pessoas mexem no mesmo job, teste conflitos. Se teme “funcionou no escritório”, priorize seu fluxo de depuração em campo.
FAQ
Offline-first significa que o app continua útil sem conexão: ele carrega os dados necessários, aceita novas entradas e mantém cada alteração segura até que a sincronização seja possível. A promessa chave é que os usuários não perdem trabalho nem confiança quando o sinal some, o sistema mata o app ou a bateria acaba no meio de uma tarefa.
SQLite costuma ser a escolha mais segura quando você precisa de filtros complexos, consultas no estilo relatório, relacionamentos muitos-para-muitos e inspeção ad hoc com ferramentas comuns. Realm faz sentido quando você quer acesso via objetos, gravações transacionais por padrão e consegue alinhar suas consultas às forças do Realm.
Trate migrações como um recurso central, não como uma tarefa única. Instale uma versão antiga, gere dados realistas no dispositivo, atualize e confirme que o app ainda abre, edita e sincroniza; teste também datasets grandes, pouco armazenamento e matar o app no meio da migração.
Adicionar um campo costuma ser simples em ambos, mas renomear e dividir campos é o que mais dá problemas. Planeje essas mudanças, defina defaults sensatos, trate nulls com cuidado e evite migrações que reescrevam todos os registros de uma vez em aparelhos antigos.
As telas de listagem e filtros que refletem o trabalho real são o termômetro: “jobs de hoje”, “formulários não sincronizados”, “editados nas últimas 2 horas” e busca rápida. Se expressar essas consultas for desconfortável, a interface ficará lenta ou o código ficará difícil de manter.
Nem SQLite nem Realm resolvem conflitos por si só; você precisa de regras de negócio. Comece escolhendo uma regra clara por tipo de entidade (last write wins, merge por campo ou fila de revisão manual) e faça o app capaz de explicar o que aconteceu quando dois dispositivos mudam o mesmo registro.
Registre metadata suficiente para explicar e reproduzir mudanças: ID do usuário, ID do dispositivo, timestamps e um marcador “última alteração” por registro. Mantenha um log de operações local para ver o que foi enfileirado, o que foi enviado, o que falhou e o que o servidor aceitou.
SQLite é fácil de inspecionar porque é um arquivo que você pode puxar do dispositivo e consultar diretamente, o que ajuda em análises ad hoc e exportações. A inspeção no Realm costuma ser mais natural para grafos de objetos e relacionamentos, mas equipes acostumadas com SQL podem achar a análise mais limitada.
Os maiores riscos de perda de dados geralmente estão na lógica do app: gravações em múltiplas etapas sem um ponto claro de commit, falhas de sincronização escondidas e ausência de caminho de recuperação para disco cheio ou corrupção. Use transações/checkpoints, mostre status claros de gravação e sincronização e ofereça uma opção previsível de “reset e resync”.
Construa um cenário end-to-end realista e cronometre: primeiro carregamento com milhares de registros, busca, gravações longas e uma simulação de “um dia offline depois reconectar” para sincronizar. Valide upgrades a partir de pelo menos duas versões antigas, simule conflitos com dois dispositivos e confirme que é possível inspecionar o estado local e os logs quando algo der errado.


