
App de entrega: arquitetura e decisões técnicas
Apps de entrega parecem simples de fora: o cliente faz o pedido, vê o entregador no mapa, recebe a notificação quando chegou. Essa simplicidade aparente esconde uma arquitetura técnica que é mais exigente do que a maioria dos apps de consumo. Geolocalização em background consome bateria e exige permissões especiais. Rastreamento em tempo real requer uma camada de comunicação persistente com o servidor. O estado de um pedido muda constantemente e precisa ser refletido de forma consistente em três apps diferentes — cliente, entregador e restaurante ou loja.
Cada uma dessas decisões técnicas tem tradeoffs. Entendê-los antes de começar a construir é o que separa um MVP que vai a produção de um que fica preso em retrabalho.
Geolocalização em Background: Configuração e Bateria
Rastrear a posição do entregador em tempo real enquanto o app está em background é um dos requisitos mais sensíveis de um app de delivery. Tanto iOS quanto Android têm restrições rigorosas sobre acesso à localização quando o app não está em foreground — e por boas razões: é um recurso que drena bateria e levanta questões de privacidade.
No iOS, o acesso à localização em background requer:
- A permissão
NSLocationAlwaysAndWhenInUseUsageDescriptionnoInfo.plist - Ativar o Background Mode "Location updates" no Xcode
- Usar
startUpdatingLocation(nãorequestLocation) para updates contínuos
No Android, além da permissão ACCESS_BACKGROUND_LOCATION, a partir do Android 10 o usuário precisa conceder explicitamente o acesso "o tempo todo" — e o sistema exige que o app explique por que isso é necessário antes de levar o usuário às configurações.
Com Expo Location:
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const LOCATION_TASK = 'background-location-task';
// Define a task que roda em background
TaskManager.defineTask(LOCATION_TASK, async ({ data, error }) => {
if (error) {
console.error('Erro de localização em background:', error);
return;
}
if (data) {
const { locations } = data as { locations: Location.LocationObject[] };
const location = locations[locations.length - 1];
// Envia para o servidor
await updateDeliveryLocation({
lat: location.coords.latitude,
lng: location.coords.longitude,
accuracy: location.coords.accuracy,
timestamp: location.timestamp,
});
}
});
async function startBackgroundTracking() {
const { status } = await Location.requestBackgroundPermissionsAsync();
if (status !== 'granted') {
// Notifique o entregador que o rastreamento precisa da permissão
return;
}
await Location.startLocationUpdatesAsync(LOCATION_TASK, {
accuracy: Location.Accuracy.Balanced, // não usar High — drena bateria
timeInterval: 5000, // atualiza a cada 5 segundos
distanceInterval: 10, // ou a cada 10 metros de movimento
deferredUpdatesInterval: 3000,
showsBackgroundLocationIndicator: true, // barra azul no iOS
foregroundService: { // Android: mantém processo vivo
notificationTitle: 'Entregando pedido #4521',
notificationBody: 'Seu rastreamento está ativo',
},
});
}
A escolha de Accuracy.Balanced em vez de High é intencional. Accuracy alta usa GPS contínuo e consome 30–40% mais bateria. Para a maioria dos casos de delivery, a precisão de 50–100m é suficiente para mostrar o entregador no mapa do cliente.
Rastreamento em Tempo Real com WebSockets
HTTP request-response não é adequado para rastreamento em tempo real. Fazer polling (um request a cada N segundos) funciona, mas é ineficiente — a maioria dos requests retorna "nenhuma novidade". WebSockets mantêm uma conexão persistente e bidirecional, enviando dados apenas quando há mudanças.
A arquitetura típica de rastreamento:
- App do entregador envia posição para o servidor a cada 5–10 segundos via HTTP POST
- Servidor atualiza a posição no banco e publica no canal WebSocket do pedido
- App do cliente, conectado ao mesmo canal, recebe o update e atualiza o marcador no mapa
// hooks/useDeliveryTracking.ts
import { useEffect, useRef, useState } from 'react';
interface DeliveryLocation {
lat: number;
lng: number;
updatedAt: number;
}
export function useDeliveryTracking(orderId: string) {
const [location, setLocation] = useState<DeliveryLocation | null>(null);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(
`wss://api.seuapp.com/tracking/${orderId}?token=${getToken()}`
);
ws.onopen = () => setConnected(true);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'location_update') {
setLocation({
lat: data.lat,
lng: data.lng,
updatedAt: data.timestamp,
});
}
};
ws.onclose = () => {
setConnected(false);
// Reconexão automática com backoff exponencial
setTimeout(() => {
// reconectar
}, 3000);
};
wsRef.current = ws;
return () => ws.close();
}, [orderId]);
return { location, connected };
}
Para escala maior, use Socket.io (que tem reconexão automática e rooms) ou serviços como Ably, Pusher ou AWS API Gateway WebSockets. Gerenciar infraestrutura de WebSockets para milhares de conexões simultâneas é complexo — serviços gerenciados resolvem isso.
Estado de Pedidos: Máquina de Estados vs Redux
Um pedido de delivery passa por múltiplos estados: aguardando confirmação, confirmado pelo restaurante, em preparo, saiu para entrega, chegou ao destino. Cada transição tem regras de negócio — um pedido "em preparo" não pode ir direto para "cancelado" sem passar por um fluxo de cancelamento, por exemplo.
Modelar isso como uma máquina de estados explícita torna as transições válidas visíveis e as inválidas impossíveis:
// Usando XState para máquina de estados do pedido
import { createMachine, assign } from 'xstate';
type OrderStatus =
| 'pending'
| 'confirmed'
| 'preparing'
| 'ready_for_pickup'
| 'out_for_delivery'
| 'delivered'
| 'cancelled';
const orderMachine = createMachine({
id: 'order',
initial: 'pending',
states: {
pending: {
on: {
CONFIRM: 'confirmed',
CANCEL: 'cancelled',
},
},
confirmed: {
on: {
START_PREPARING: 'preparing',
CANCEL: 'cancelled',
},
},
preparing: {
on: {
READY: 'ready_for_pickup',
},
},
ready_for_pickup: {
on: {
PICKED_UP: 'out_for_delivery',
},
},
out_for_delivery: {
on: {
DELIVERED: 'delivered',
},
},
delivered: { type: 'final' },
cancelled: { type: 'final' },
},
});
| Abordagem | Vantagem | Desvantagem |
|---|---|---|
| Máquina de estados (XState) | Transições explícitas, bugs impossíveis por design | Curva de aprendizado inicial |
| Redux com actions | Familiar, ecossistema maduro | Transições inválidas são possíveis em código |
| Zustand simples | Mínimo boilerplate | Sem garantias de transição |
| Estado no servidor (polling) | Simples no cliente | Latência, custo de requests |
Para apps de delivery, a máquina de estados é a abordagem mais defensiva. As transições de status de pedido têm implicações financeiras (reembolsos, pagamentos ao entregador) — não é aceitável que o estado fique inconsistente por um bug de gerenciamento de estado.
Notificações de Status: Cada Transição Importa
Cada mudança de estado do pedido é uma oportunidade de comunicação que reduz ansiedade do cliente e aumenta a confiança no produto. Pedidos que chegam sem notificações intermediárias geram chamadas para o suporte. Pedidos com notificações claras a cada transição geram avaliações positivas.
As notificações de status devem ser:
- Específicas: "Seu pedido #4521 saiu para entrega com João" é melhor que "Pedido em caminho"
- Acionáveis: quando relevante, inclua um deep link para a tela de rastreamento
- Oportunas: a notificação deve chegar em segundos após a transição, não minutos
- Não excessivas: não notifique estados intermediários do backend que não são relevantes para o usuário
Para o app do entregador, as notificações têm um papel ainda mais crítico: novo pedido disponível, cliente ligou, pedido cancelado. Essas notificações precisam chegar com prioridade alta e acionar o app mesmo quando em background.
No servidor, o fluxo é: mudança de estado do pedido → evento no message broker (RabbitMQ, SQS, Kafka) → serviço de notificações → FCM/APNs → dispositivo. Esse desacoplamento garante que uma falha no serviço de notificações não afete o processamento dos pedidos.
Conclusão
Apps de delivery são tecnicamente exigentes porque combinam três problemas distintos: localização em tempo real (hardware e permissões), comunicação bidirecional persistente (WebSockets e infraestrutura) e lógica de negócio com estados críticos (máquina de estados e consistência). Tratar cada um desses problemas com a seriedade que merecem é o que diferencia um app de delivery confiável de um que funciona na demo e quebra na produção.
Na SystemForge, apps com requisitos técnicos complexos como delivery, logística e rastreamento são planejados com a arquitetura correta desde o início — porque refatorar a camada de geolocalização ou migrar de polling para WebSockets depois do lançamento é muito mais caro do que projetar certo desde o início. Se você está construindo um app de delivery ou qualquer produto que dependa de rastreamento em tempo real, nossa equipe tem a experiência técnica para ajudar a tomar as decisões certas.
Precisa de um Aplicativo Mobile?
Desenvolvemos apps iOS e Android com React Native ou Flutter.
Saiba mais →Precisa de ajuda?
