Executando verificação de segurança...
5

Arquitetando um App de Inventário Offline-First em React Native: Performance, Concorrência e Escalabilidade com 5.000+ Itens

Desenvolvi para uma empresa um Sistema de Inventário de Ativos, onde fui responsável pela arquitetura e implementação do app mobile utilizado em campo para execução do inventário.

O app precisava lidar com certos desafios:

  • Leitura de código de barras e QR Code
  • Listas grandes (5.000+ itens)
  • Sincronização offline com múltiplos usuários
  • Filtros e buscas locais em tempo real

No início parecia algo simples, mas conforme o projeto evoluiu, surgiram desafios técnicos interessantes principalmente relacionados a performance, concorrência e estabilidade em dispositivos Android de baixo RAM.

Abaixo, compartilho algumas decisões arquiteturais e técnicas que fizeram diferença real.

Stack utilizada

  • React Native com Expo (New Architecture)
  • Drizzle ORM com expo-sqlite
  • Zustand para estado global
  • React Hook Form + Zod para formulários
  • NativeWind para UI
  • FlashList para listas massivas
  • react-native-vision-camera para leitura de códigos QR/BARCODE

Filtros e Buscas Locais

Logo na primeira tela, o usuário visualiza uma lista extensa de ativos que pode ser:

  • Atualizada por sincronização.
  • Filtrada por digitação.
  • Buscada por leitura de código de barras.

O primeiro passo foi substituir a FlatList por FlashList, garantindo melhor virtualização e reaproveitamento de views.
Mas isso não era suficiente.

Se eu fizesse um .filter() em memória a cada tecla digitada, a JS thread sofreria bastante principalmente com milhares de registros.

A solução foi delegar completamente as buscas ao banco
fiz a criação de índices no SQLite para colunas como barcode, name e campos de status.
As buscas passaram a acontecer diretamente no banco. Também apliquei debounce, com um pequeno atraso para que a busca fosse executada, evitando múltiplas chamadas desnecessárias enquanto a pessoa ainda está digitando.
Utilizei paginação com LIMIT/OFFSET, e o resultado foi buscas praticamente instantâneas, mesmo com milhares de registros locais.

Leitura de Código de Barras

Para leitura contínua com boa performance, utilizei react-native-vision-camera.
Ele utiliza processamento mais próximo do nativo e entrega desempenho significativamente superior ao expo-camera em cenários de leitura frequente.
Após a leitura do código, a busca ocorre direto no banco indexado, garantindo retorno em milissegundos.

Upload de Imagens sem Pico de Memória

Cada ativo podia conter fotos que precisavam ser enviadas via URL assinada.
Inicialmente eu convertia a imagem para Base64 antes de enviá-la.
Isso gerava:

  • Aumento de tamanho (~33%)
  • Pico de memória na JS thread
  • Risco real de crash em dispositivos mais simples

A solução foi utilizar upload nativo direto do disco:

await FileSystem.uploadAsync(uploadUrl, uri, {
  httpMethod: 'POST',
  uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT,
  headers: { 'Content-Type': 'image/jpeg' },
});

O que isso garantiu foi uma leitura direta do arquivo em disco, upload executado fora da JS thread, sem conversão intermediária, melhor aproveitamento de memória e, além disso, o uso de streaming nativo, garantindo maior estabilidade.

Arquitetura Offline-First: Sincronização Incremental e Resolução de Conflitos

Garantir que múltiplos usuários realizassem inventário simultaneamente e offline elevou bastante a complexidade.

Usar o Drizzle ORM com SQLite facilitou bastante a implementação, tanto pela sua simplicidade de uso quanto pelo sistema de migração extremamente simples.

Eu precisava garantir que o aplicativo funcionasse offline e que, ainda assim, os ativos estivessem atualizados.

A arquitetura foi baseada em três pilares:

Delta Sync

Ao voltar a ficar online, o app envia um cursor (updatedAfter) para a API e consome estritamente os deltas apenas dados modificados desde o último sync.

Isso reduziu:

  • tráfego desnecessário
  • tempo de sincronização
  • reprocessamento completo do dataset

Action Queue + Optimistic UI

A UI não é bloqueada aguardando a rede.

Cada alteração:

  • Reflete imediatamente na interface
  • Entra em uma fila local
  • Um listener em background envia os dados em batch ao detectar conexão

O usuário nunca fica esperando a internet para continuar operando.

Tombstones + Last-Write-Wins (LWW)

Implementei a resolução de conflitos baseada em timestamp.
Quando um item é excluído offline, ele recebe uma flag deletedAt (tombstone). No backend, utilizei a estratégia Last-Write-Wins para decidir qual versão prevalece.
Com isso, os conflitos passam a ser resolvidos de forma determinística, garantindo a integridade dos dados mesmo com múltiplos dispositivos modificando os mesmos ativos.

Listas Grandes (5.000+ itens)

A parte em que mais trabalhei foi garantir a performance dessa lista. O uso da FlashList foi importante, mas precisei complementar com outras estratégias:

Paginação no SQLite, evitando carregar tudo de uma vez;

Uso de useCallback no renderItem. Mesmo já utilizando o React Compiler, optei por manter o useCallback para garantir maior previsibilidade no comportamento dos renders.

const renderItem = useCallback(
  ({ item }: ListRenderItemInfo<Asset>) => {
    return <AssetItem data={item} />
  },
  []
);

No AssetItem, eu usei React.memo para evitar re-renderizações desnecessárias quando as props não mudavam, já que, em alguns cenários, apenas um item específico era alterado.

O resultado foi um scroll estável, mesmo com milhares de registros armazenados localmente.

Para mim, ficou claro que o React Native é totalmente capaz de suportar aplicações enterprise complexas, com grandes volumes de dados, operação offline real, upload de mídia e sincronização concorrente.

No fim, o diferencial não está apenas na tecnologia escolhida, mas nas decisões arquiteturais que você toma. Delegar o processamento pesado ao banco, evitar sobrecarga na JS thread, controlar re-renderizações com precisão e sincronizar apenas o necessário fazem toda a diferença.

Essas foram apenas algumas das decisões que tomei. Houve várias outras que também contribuíram para que o app operasse sem problemas.

Espero que isso possa, de alguma forma, gerar algum insight para quem está passando por desafios parecidos.

Carregando publicação patrocinada...
1

Que massa ver esse poster, numa época em que estou tentando estudar o React Native com Expo para resolver algumas ideias pessoais.

Admito que não entendi muita coisa técnica que se falou kkk, mas isso me deu um prazer de aprender ainda mais as bases da coisa mesmo nesse tempo de IA, aonde quase todos projetos que tenho para desenvolver podem ser feitos apenas conduzindo prompts, até porque, os projetos são para uso pessoal mesmo.

Mas valeu pelo post.

1

Valeu demais pelo comentário.

No começo muita coisa parece complexa mesmo, mas é normal. O importante é continuar estudando as bases e construindo projetos pequenos para ir ganhando confiança.

IA ajuda bastante, mas entender o que está acontecendo por trás faz toda a diferença quando surge algum problema ou quando você quer evoluir algo além do básico.

Se você já está estudando React Native com Expo para seus projetos pessoais, já está no caminho certo. Continua praticando que as peças vão se encaixando naturalmente.

1
1

Gostei bastante da solução. Tive um desafio parecido recentemente e acabei usando um serviço chamado PowerSync, que resolve boa parte dessa lógica complexa que você construiu (como Action Queue, Delta Sync e Tombstones). Como o core dele é feito em C++/Rust, ele transforma essa dor de cabeça em uma solução muito robusta, conectando-se direto ao log de replicação do banco (no meu caso MongoDB, mas também funciona com muitos outros bancos). Recomendo dar uma olhada nessa tecnologia, ela é relativamente recente e faz parte de uma stack que descobri há pouco tempo (os Sync Engines). O legal é que essa sincronização automática se aplica a vários ambientes, como Web ou até Desktop com Electron. Além de ser open-source e ser possível rodar localmente com Docker fazendo self-hosted. Então, acredito que vale a pena dar uma conferida na tecnologia.

Site do powerSync:
https://www.powersync.com/