Todo app corporativo que sai do navegador e vai para o celular encontra a mesma pergunta mais cedo ou mais tarde: onde esse dado deve ficar dentro do aparelho? Se a resposta vira um grande bloco de armazenamento sem dono, o time mistura token com cache, preferencia com fila de sincronizacao, rascunho com dado sensivel e descobre o problema quando comeca a perder contexto, duplicar estado ou travar manutencao.
Persistencia local nao e detalhe de implementacao. Ela define como o app reabre, como suporta rede instavel, como protege credenciais, como monta fila offline e como envelhece sem virar uma caixa-preta. Em projeto React Native real, a pergunta nao e so qual biblioteca instalar. A pergunta e que tipo de dado voce esta guardando, com que risco, por quanto tempo e com qual regra de leitura e limpeza.
Este guia organiza uma forma pratica de decidir entre AsyncStorage, SecureStore e SQLite em apps corporativos com React Native e Expo, mantendo fronteiras mais claras entre sessao, cache, rascunho e sincronizacao.
1. Persistencia local nao e uma coisa so
Um dos erros mais comuns e tratar qualquer dado local como se fosse equivalente. No app real, existem categorias bem diferentes:
- segredos pequenos de sessao, como token e identificadores sensiveis;
- preferencias leves, como filtro, aba escolhida e ajuste visual;
- cache de leitura, para abrir a tela mesmo com rede ruim;
- rascunhos e formularios em andamento;
- fila local de operacoes pendentes;
- conjuntos estruturados de dados que precisam consulta, ordenacao e estado.
Quando tudo vai para a mesma camada por conveniencia, o time perde clareza sobre retencao, seguranca, consulta e evolucao do dado. O artigo offline, fila e sincronizacao com API ja mostrava que o app precisa separar o que e sessao, cache, rascunho e fila. Aqui o foco e transformar essa separacao em decisao concreta de persistencia.
2. Comece pelo tipo de dado, nao pela biblioteca
Escolher armazenamento antes de classificar o dado costuma gerar retrabalho. A biblioteca so vem depois da resposta para algumas perguntas:
- o dado e sensivel ou apenas operacional?
- ele e pequeno ou cresce com o uso?
- precisa busca por lista, filtro, ordenacao ou status?
- precisa sobreviver ao fechamento do app?
- precisa ser limpado no logout?
- e default local, cache temporario ou fonte operacional para sincronizacao?
Essas perguntas ajudam mais do que qualquer comparativo superficial de pacote. Um filtro salvo de lista nao pede o mesmo tratamento que uma fila de envio com cem itens e status diferentes.
3. SecureStore entra para segredo pequeno, nao para tudo
A documentacao do Expo SecureStore descreve a biblioteca como uma forma de armazenar pares chave e valor com protecao apropriada para informacoes pequenas e sensiveis. A mesma documentacao tambem lembra um detalhe importante no Android: dados do SecureStore nao devem ser tratados como se fossem restauraveis de qualquer backup, porque a chave local pode deixar o conteudo inutilizavel depois de certas mudancas no aparelho.
Na pratica, SecureStore costuma servir bem para:
- access token e refresh token;
- identificador de sessao ou tenant com sensibilidade maior;
- sinal pequeno de estado de autenticacao protegido;
- segredo curto que nao deveria ficar em armazenamento comum.
Ele normalmente nao e o lugar ideal para:
- listas grandes;
- cache de leitura de telas inteiras;
- fila de sincronizacao com varios itens;
- rascunho extenso de formulario;
- qualquer bloco grande de dados que o time vai consultar o tempo todo.
Esse desenho conversa diretamente com autenticacao mobile com token e refresh. Credencial pequena e sensivel em camada protegida. Estado operacional maior em armazenamento mais adequado.
4. AsyncStorage funciona bem para chave-valor simples e nao sensivel
A referencia do AsyncStorage na documentacao da Expo resume a proposta de forma direta: armazenamento persistente, assicrono, sem criptografia e baseado em chave-valor. Isso e suficiente para varios cenarios cotidianos do app, desde que a equipe nao tente empurrar para ele responsabilidades que pertencem a outra camada.
AsyncStorage costuma ser boa escolha para:
- preferencias do usuario;
- ultimo filtro usado;
- estado leve de onboarding;
- flags locais nao sensiveis;
- pequenos caches simples quando consulta estruturada nao faz falta.
Evite usar AsyncStorage como se fosse:
- cofre de segredo;
- banco relacional improvisado;
- fila complexa com muitos itens e estados;
- cache gigante de listas que voce precisa paginar, ordenar e limpar com criterio.
Ele resolve bem o que e simples. O problema nasce quando o time transforma chave-valor em mini banco sem schema, sem indice e sem dono.
5. SQLite entra quando o dado ganha estrutura, volume ou ciclo operacional
A documentacao do Expo SQLite explica que a biblioteca da acesso a um banco SQLite persistido entre reinicios do app. Isso muda bastante a conversa. Quando o aplicativo precisa gravar colecoes estruturadas, consultar por status, ordenar por data, cruzar entidades ou sustentar fila local de verdade, SQLite passa a ser candidata natural.
SQLite costuma fazer sentido para:
- cache de leitura estruturado;
- rascunhos com varios campos e anexos relacionados;
- fila local com status, tentativas e erros;
- listas sincronizadas que exigem filtro e ordenacao local;
- estado offline que precisa consulta incremental ou limpeza seletiva.
A propria documentacao tambem ajuda a evitar erro comum: db.execAsync() pode ser util para lotes, mas nao escapa parametros; quando ha entrada dinamica, prepared statements e chamadas parametrizadas sao o caminho mais seguro. Em app corporativo, isso importa porque logo aparecem filtros, textos livres, observacoes e payloads parciais que nao deveriam ser interpolados na mao.
6. Um mapa pratico de decisao ajuda mais do que preferencia pessoal
Uma divisao simples costuma funcionar bem em boa parte dos projetos:
- SecureStore: segredos pequenos e credenciais sensiveis;
- AsyncStorage: preferencia leve, sinal local simples, estado pequeno nao sensivel;
- SQLite: cache estruturado, rascunho, fila local, historico operacional e dados que pedem consulta real.
Essa regra nao e lei universal, mas da uma fronteira inicial saudavel. O principal ganho nao e teorico. E operacional: qualquer pessoa do time entende mais rapido por que um token foi para uma camada e um rascunho grande foi para outra.
7. Sessao, cache, rascunho e fila local nao devem compartilhar a mesma vida util
Outro erro frequente e aplicar a mesma politica de permanencia a tudo. Cada bloco de dado local envelhece de forma diferente:
- sessao pode expirar ou ser invalidada no logout;
- cache de leitura pode ser renovado com base em staleness;
- rascunho pode durar dias ate o usuario concluir;
- fila local pode permanecer ate sincronizar ou falhar definitivamente.
Quando a vida util de tudo e igual, o time ou apaga demais ou retira de menos. Resultado: usuario perde trabalho em andamento ou carrega lixo antigo por tempo demais.
8. Persistencia local tambem precisa de schema e migracao
No momento em que o app usa SQLite de forma seria, o armazenamento local deixa de ser detalhe invisivel. Ele passa a ter schema, versao e migracao. Nao precisa virar uma plataforma paralela, mas precisa de disciplina minima.
Perguntas uteis:
- quais tabelas e campos existem localmente?
- como a estrutura evolui entre releases?
- o que acontece quando um campo antigo deixa de existir?
- como limpar dado obsoleto sem corromper fila ou cache?
- como reconstruir parte do estado se o schema mudar?
Ignorar isso por muito tempo faz o app acumular dado morto e comportamento imprevisivel entre versoes.
9. Fila local pede estrutura melhor do que um JSON gigante
Quando a equipe comeca a guardar operacoes offline, um anti-pattern aparece rapido: salvar tudo em uma unica chave como um array imenso serializado. No comeco parece simples. Depois surgem status, tentativas, erro por item, reenvio seletivo, limpeza parcial e prioridade. Nesse momento, a estrutura de banco local tende a respirar melhor do que um blob crescente.
Por isso, o artigo background task e sincronizacao diferida conversa tao bem com este tema. Task em segundo plano sem camada local organizada vira promessa fraca. O scheduler ate pode acordar o app, mas sem fila estruturada a execucao continua confusa.
10. Cache de leitura nao precisa ser identico ao modelo do backend
Uma tentacao comum e copiar o payload inteiro da API e chamalo de persistencia. Nem sempre isso e o melhor desenho. Em varios cenarios, vale montar um modelo local mais util para o app:
- manter apenas os campos necessarios para a tela;
- gravar carimbo de ultima sincronizacao;
- separar resumo e detalhe;
- registrar status local, como pendente ou stale.
Esse tipo de modelagem reduz peso, simplifica limpeza e melhora leitura local. Persistencia madura nao e uma copia cega do backend. E uma camada pensada para o comportamento do app.
11. Remote config e storage local precisam conviver bem
O artigo feature flags e configuracao remota no app reforcou a ideia de defaults locais seguros. Isso vale muito aqui. Se o app depende de parametros remotos, ele ainda precisa decidir onde guarda a ultima configuracao valida, por quanto tempo, em que formato e o que acontece quando a rede nao entrega nada novo.
Em varios casos, AsyncStorage basta para guardar parametros leves e nao sensiveis. Em outros, valores operacionais podem ser aplicados a entidades em SQLite. O importante e o app conseguir distinguir:
- configuracao remota atual;
- default embarcado;
- estado operacional derivado dessa configuracao.
Sem essa separacao, suporte e engenharia nao sabem mais o que veio da rede, o que veio do binario e o que nasceu de cache antigo.
12. Performance local tambem conta
Persistencia ruim nao machuca so arquitetura. Machuca UX. Ler um bloco enorme em toda abertura de tela, desserializar colecoes gigantes ou fazer consultas mal pensadas pode deixar o app pesado mesmo offline.
Alguns cuidados ajudam:
- ler so o necessario para a tela atual;
- separar resumo de detalhe;
- evitar hydratar tudo de uma vez sem motivo;
- limpar cache morto periodicamente;
- nao misturar persistencia com renderizacao descuidada.
Essa conversa encontra o artigo performance em React Native com FlatList e imagens. Quando lista, cache e renderizacao crescem juntas, o problema nunca fica isolado em um unico pacote.
13. Logout e limpeza precisam ser intencionais
Se o usuario troca de conta ou sai do app, a equipe precisa saber o que deve sumir e o que pode permanecer. Exemplos:
- token e segredo sensivel devem sair;
- fila local ligada ao usuario anterior normalmente deve sair ou ser arquivada com regra clara;
- cache de leitura sensivel talvez precise limpeza;
- preferencias neutras de interface podem continuar.
Quando logout so troca de rota, o app carrega vestigio de contexto antigo e abre brecha para erro operacional e privacidade ruim.
14. Teste os cenarios que realmente quebram
Uma rotina minima de validacao de persistencia local deveria incluir:
- fechar e reabrir o app com fila pendente;
- trocar de conta e verificar limpeza;
- simular atualizacao de versao com dado local existente;
- cortar a rede e continuar lendo cache;
- restaurar rede e sincronizar em etapas;
- validar aparelho mais lento e com armazenamento mais cheio;
- forcar falha de API e conferir se o dado local continua coerente.
Sem esse teste, a persistencia parece boa apenas no fluxo bonito do simulador.
15. Um checklist rapido para decidir sem bagunca
- Classifique o dado antes de escolher a biblioteca.
- Use SecureStore apenas para segredo pequeno e sensivel.
- Use AsyncStorage para chave-valor simples e nao sensivel.
- Use SQLite quando houver estrutura, consulta, fila ou cache maior.
- Defina vida util diferente para sessao, cache, rascunho e fila.
- Trate schema e migracao local como parte do app.
- Planeje limpeza de logout e descarte de dado obsoleto.
- Teste app fechado, reaberto, offline, atualizado e com falha de API.
16. Quando vale um diagnostico tecnico
Se o app ja sofre com estado local misturado, sincronizacao imprevisivel, fila sem estrutura, segredo salvo na camada errada ou retrabalho a cada nova tela offline, talvez a persistencia local esteja pedindo desenho mais consciente. Nessa hora, um diagnostico tecnico ajuda a reorganizar fronteira entre mobile, API, cache, seguranca e sincronizacao antes que o crescimento do app transforme armazenamento local em passivo permanente.
Persistencia local madura nao e a que usa mais tecnologia. E a que deixa claro o lugar de cada dado, o ciclo de vida de cada camada e o comportamento do app quando a rede, a sessao ou a versao mudam.
Referencias editoriais: Expo SQLite, Expo SecureStore e AsyncStorage na documentacao da Expo.
Termos tecnicos desta leitura
Alguns conceitos aparecem com frequencia neste tema. Abrir o glossario ajuda a comparar definicoes, exemplos e leituras relacionadas sem sair do contexto do artigo.