Como Construir um App de Entrega de Mercado com React Native + Expo (2026)
Se você já tentou construir um app estilo Instacart do zero, sabe a dor: três apps para lançar (cliente, motorista, admin), uma camada de rastreamento em tempo real, pagamentos, despacho e o sempre divertido "lidar com um motorista que sai da linha durante a entrega".
Aqui está uma pilha prática e uma sequência de construção que mantém as partes móveis ao mínimo.
A realidade dos três apps
Um produto de entrega de mercados é três apps compartilhando uma única back-end:
- App do cliente — navegar, carrinho, checkout, rastreamento
- App do motorista — aceitar pedidos, navegar, capturar prova de entrega
- Painel administrativo — estoque, despacho, disputas
Junte-os em um e você passará o próximo trimestre desentrelaçando lógica de permissão. Mantenha-os separados.
A pilha que converge
- Móvel: React Native + Expo (um repositório de código, iOS + Android, atualizações OTA)
- Back-end: Supabase (Postgres, autenticação, em tempo real, RLS)
- Pagamentos: Stripe Payment Intents + Stripe Connect para pagamentos aos motoristas
- Mapeamento: Google Maps SDK + API de Rotas + Matriz de Distâncias
- Push: Expo Push → Twilio SMS para alertas críticos
- Em tempo real: Canais em tempo real do Supabase (nenhum servidor WebSocket personalizado)
Modelo de dados — as partes que importam
-- O detalhe crítico: capturar o preço no momento da compra
create table order_items (
id uuid primary key default gen_random_uuid(),
order_id uuid references orders(id),
product_id uuid references products(id),
quantity int not null,
price_at_purchase int not null, -- nunca junte de volta a products.price_cents
substitution_preference text check (
substitution_preference in ('allow','contact','refund')
)
);
-- Log de auditoria somente para adicionar = fonte gratuita da verdade sobre o cronograma de status
create table order_events (
id uuid primary key default gen_random_uuid(),
order_id uuid references orders(id),Duas invariantes que vale a pena gravar na memória:
- Nunca resolva
order_items.price fazendo join com o products. Preços mudam; recibos não devem.
- Não confie no relógio do cliente para
delivered_at. Use now() no Postgres. Você agradecerá na primeira vez que uma disputa chegar à sua caixa de entrada.
Rastreamento em tempo real sem dor
Lado do motorista: capture a localização a cada 5-15 segundos, diminua quando parado.
import as Location from 'expo-location';
import { supabase } from './supabase';
await Location.startLocationUpdatesAsync('driver-location', {
accuracy: Location.Accuracy.Balanced,
timeInterval: 10_000,
distanceInterval: 30,
foregroundService: {
notificationTitle: 'Entrega em andamento',
notificationBody: 'Compartilhando sua localização com o cliente',
},
});
Lado do cliente: assine a linha do motorista.
useEffect(() => {
const channel = Escriver isso do zero é um esforço de 6 a 8 semanas. Se você quiser pular o JSX, os prompts para um construtor de aplicativos móveis AI geram esses códigos em uma tarde e exportam React Native limpo que você pode estender manualmente. Estou usando RapidNative para isso e embarcando a UI chata em um dia, então posso passar o resto do sprint na lógica de despacho.
Os erros sutis a evitar
- Timestamps do lado cliente. Sempre use
now() no banco de dados.
- Acreditar que o aplicativo do motorista atualiza o status do pedido. Use gatilhos do Postgres em
order_events para atualizar orders.status atomicamente.
- Ler estoque de
products.inventory_count no checkout. Condições de corrida. Use uma transação serializável ou SELECT ... FOR UPDATE.
- Renderizar o mapa em cada ping de localização. Atenuar atualizações de marcadores.
Conclusão
React Native + Expo + Supabase + Stripe + Google Maps é chato, bem documentado e consolidado por uma razão. As vitórias nesse espaço vêm de densidade operacional e frescor, não da pilha tecnológica. Lance o MVP, coloque-o na frente de dez clientes, itere.
Se você quiser toda a arquitetura em um lugar (modelo de dados, lista de telas, sequência de prompts), o caminho completo está no nosso blog.

