DEV Community

Cristian Dornelles
Cristian Dornelles

Posted on

Deferred Deep Links: O Clique Aconteceu. A Instalação Também. E Agora? (Parte 6)

Header

O que construímos até aqui

Nas últimas partes da série, saímos do zero e chegamos a um sistema funcional: configuramos a captura nativa no Android (Post 2) e no iOS (Post 3), integramos tudo ao Flutter com DeepLinkService, MethodChannel e EventChannel (Post 4), e publicamos os arquivos de verificação em deeplinkslab.dev para que App Links e Universal Links funcionem em produção (Post 5).

O link funciona. Mas só quando o app está instalado.


Cenário: João recebe um link de indicação do FitConnect. Clica. Mas ele ainda não tem o app instalado.

O que acontece com o referralCode enquanto ele passa pela loja, faz o download e abre o app pela primeira vez?

Com deep links normais: nada. O contexto se perde. O app abre na tela inicial, sem nenhuma pista de onde João veio.

É exatamente esse problema que os Deferred Deep Links resolvem.

Este é o sexto conteúdo de uma série completa sobre Deep Links no Flutter. Se você ainda não viu os posts anteriores: Post 1 — Guia para Iniciantes | Post 2 — Android com Kotlin | Post 3 — iOS com Swift | Post 4 — Integração Flutter | Post 5 — Produção.


Neste artigo você vai aprender:

  • Por que deep links normais falham quando o app não está instalado.
  • Solução simples com LocalStorage.
  • Solução robusta com backend e fingerprint.

O desafio

O fluxo do problema é direto:

  1. Usuário clica no link — app não está instalado.
  2. Navegador abre a loja (Play Store / App Store).
  3. Usuário instala o app e abre pela primeira vez.
  4. ❓ Como o app sabe qual era o link original?

Aqui está o nó: quando o app é aberto diretamente da loja após a instalação, não há nenhum intent nem URL sendo entregue ao sistema. O deep link original ficou no navegador — e o app nunca teve acesso a ele.

A resposta para esse problema é Deferred Deep Links: uma estratégia para preservar o contexto do link entre o clique e o primeiro launch do app.

Fluxo do problema

Fluxo do problema: do clique à instalação


Solução 1: LocalStorage

A abordagem mais simples usa o próprio device para guardar o estado. A ideia: quando a página web de redirect é aberta (veremos isso no Post 7), ela salva o referralCode no localStorage do navegador. Na primeira abertura do app, o Flutter verifica se existe um estado pendente salvo localmente e repassa o dado ao Flutter.

No Flutter, a lógica fica no DeepLinkService:

// lib/services/deep_link_service.dart

class DeepLinkService {
  // Salvar código após primeira abertura
  Future<void> processDeferredLink() async {
    if (initialLink != null &&
        initialLink!.contains('referralCode')) {

      final code = _extractReferralCode(initialLink!);
      await _storage.write('pending_referral', code);
    }
  }

  // Recuperar código no signup
  Future<String?> getPendingReferralCode() async {
    final code = await _storage.read('pending_referral');

    if (code != null) {
      await _storage.remove('pending_referral'); // Cleanup após uso
      return code;
    }

    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

A integração acontece em dois momentos distintos do ciclo de vida do app:

// SplashScreen — primeiro launch
await deepLinkService.processDeferredLink();

// SignupScreen — ao carregar a tela
final pendingCode = await deepLinkService.getPendingReferralCode();
if (pendingCode != null) {
  referralCodeController.text = pendingCode;
}
Enter fullscreen mode Exit fullscreen mode

O remove após a leitura é importante: garante que o código seja usado apenas uma vez. Sem isso, o código reapareceria em toda abertura do app até o signup ser concluído.

Vantagens:

  • ✅ Simples de implementar.
  • ✅ Funciona sem conexão.
  • ✅ Sem dependência de backend.

Limitações:

  • ⚠️ Essa abordagem funciona melhor quando o fluxo continua dentro do mesmo contexto web/app híbrido.
  • ⚠️ Sem analytics — não há como saber quantos usuários chegaram via deferred link.

Solução 2: Backend com fingerprint

Quando você precisa de rastreamento cross-device ou analytics de conversão, a solução é mover o estado para o servidor.

O conceito central é o fingerprint: uma combinação de atributos do request HTTP (IP, User-Agent, Accept-Language) que, com alta probabilidade, identifica o mesmo dispositivo em dois momentos diferentes — o clique no link e o primeiro launch do app.

O fluxo no backend:

// Backend: Criar short link
app.post("/api/referral/create-link", async (req, res) => {
  const { referralCode } = req.body;
  const shortCode = generateShortCode();

  await db.save(shortCode, referralCode);

  res.json({
    shortUrl: `https://deeplinkslab.dev/s/${shortCode}`,
  });
});

// Backend: Processar clique
app.get("/s/:code", async (req, res) => {
  const referralCode = await db.get(req.params.code);
  const fingerprint = generateFingerprint(req); // IP + User-Agent + Accept-Language

  // Salvar em cache por 48h
  await redis.setex(`pending:${fingerprint}`, 172800, referralCode);

  // Redirecionar para a loja da plataforma
  const platform = detectPlatform(req);
  res.redirect(getStoreUrl(platform));
});

// Backend: Recuperar código no primeiro launch
app.post("/api/referral/get-pending", async (req, res) => {
  const fingerprint = generateFingerprint(req);
  const code = await redis.get(`pending:${fingerprint}`);

  if (code) {
    await redis.del(`pending:${fingerprint}`); // Cleanup após uso
    res.json({ referralCode: code });
  } else {
    res.status(404).json({ message: "Not found" });
  }
});
Enter fullscreen mode Exit fullscreen mode

No Flutter, o primeiro launch faz uma chamada ao backend para verificar se há um código pendente associado ao fingerprint do device:

// lib/services/deep_link_service.dart

Future<String?> checkPendingOnBackend() async {
  try {
    final response = await dio.post('/api/referral/get-pending');
    return response.data['referralCode'];
  } catch (e) {
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Vantagens:

  • ✅ Funciona cross-device.
  • ✅ Analytics completo de cliques e conversões.
  • ✅ Mais robusto no mesmo device.

Desvantagens:

  • ⚠️ Requer backend e Redis (ou equivalente).
  • ⚠️ Mais complexo de implementar e manter.
  • ⚠️ Fingerprinting não é 100% preciso — redes com NAT ou VPN podem causar colisões.

Quando usar cada uma

Quando usar: LocalStorage vs Backend com fingerprint

Comece com LocalStorage. Se o seu caso de uso for simples — referral no mesmo device, sem necessidade de analytics — essa solução resolve com poucas linhas de código.

Migre para backend quando precisar de analytics de conversão, rastreamento cross-device, ou quando a precisão do fingerprinting for aceitável para o seu contexto de negócio.


O que construímos até aqui

Ao final desta etapa, você já tem:

  • Clareza sobre por que deep links normais não preservam contexto após instalação.
  • A solução com LocalStorage: processDeferredLink para salvar e getPendingReferralCode para recuperar.
  • A arquitetura da solução com backend: short link, fingerprint no Redis e consulta no primeiro launch.
  • Os critérios para escolher entre as duas abordagens.

Este é o sexto de 9 posts da série. Nos próximos posts, o fluxo fica completo: no Post 7, criamos a página web que serve como ponte entre o navegador e a loja — e é ela que executa a parte do LocalStorage quando o app ainda não está instalado.

No próximo post: a página web de redirect inteligente que detecta a plataforma, exibe o código de indicação visualmente e redireciona para a loja certa.


Código completo disponível no repositório: FitConnect no GitHub


Se esse conteúdo te ajudou, deixa um clap 👏 e salva o post — isso me ajuda a continuar a série.

Você implementaria LocalStorage ou backend com fingerprint?
Ou usaria um serviço pronto como AppsFlyer / Branch / Firebase?


Tags: Flutter, Deep Links, Deferred Deep Links, Mobile Development

Top comments (0)