Imagine isso: seu usuário recebe um link de desconto, clica nele — e BOOM! Ele não só abre seu app, mas já está na tela de checkout com o cupom aplicado. Isso é mágica? Não — são Deep Links!
Este é o primeiro conteúdo de uma série completa sobre Deep Links no Flutter, com novos artigos saindo semanalmente, ou quase. Vamos entender os conceitos fundamentais, explorar os diferentes tipos e preparar a estrutura base do nosso projeto do zero, sem depender de pacotes de terceiros.
Ao longo desta série, vou te mostrar como implementar deep links em Flutter — sem utilizar pacotes prontos. Por quê? Porque você vai aprender exatamente como tudo funciona por baixo dos panos.
Neste artigo, você vai aprender:
- O que são deep links.
- Diferença entre App Links e Custom Schemes.
- Como estruturar a base no Flutter.
O que são Deep Links?
Deep links são URLs que levam o usuário diretamente para uma tela específica do seu app, em vez de abrir no navegador.
Exemplo real:
- Fluxo tradicional: Abre navegador → usuário instala o app → abre → navega até a tela.
- Deep link: Abre o app diretamente na tela correta.
Para termos uma visão completa, o diagrama abaixo ilustra o roteamento (conhecido como Deferred Deep Linking), onde — até mesmo se o usuário não tiver o app — o contexto do link é preservado após ele passar pelas lojas de apps:
Fluxo completo
Esse fluxo completo, onde o usuário pode instalar o app e ainda assim manter o contexto original do link, é o que chamamos de Deferred Deep Linking.
Anatomia de um Deep Link
https://fitconnect.app/signup?referralCode=TRAINER12345
-
Scheme:
https -
Host:
fitconnect.app -
Path:
/signup -
Query:
?referralCode=TRAINER12345
Cada parte dessa estrutura tem um papel específico na navegação. O scheme identifica o protocolo (ou, no caso de custom schemes, o aplicativo), o host e o path determinam a rota, e os query parameters carregam dados extras (como um código de indicação — referralCode).
Tipos de Deep Links
Existem dois tipos principais e a escolha entre eles tem implicações diretas na segurança e na experiência do usuário.
Custom Schemes (fitconnect://)
fitconnect://fitconnect.app/signup?referralCode=TRAINER12345
- ✅ Rápido de implementar.
- ✅ Ótimo para testes locais.
- ⚠️ Menos seguro — qualquer aplicativo pode registrar o mesmo scheme.
- ⚠️ Se o app não estiver instalado, o sistema exibe um erro feio.
App Links (Android) / Universal Links (iOS)
https://fitconnect.app/signup?referralCode=TRAINER12345
- ✅ Seguro — verificação bidirecional entre aplicativo e servidor.
- ✅ Se o app não estiver instalado, abre normalmente no navegador.
- ✅ Recomendado para produção.
- ⚠️ Requer domínio próprio.
- ⚠️ Setup mais complexo.
O que é essa verificação bidirecional? O sistema operacional só abre o app se ambas as pontas se reconhecerem: o app declara quais domínios ele trata, e o servidor confirma quais aplicativos têm permissão para isso — por meio de um arquivo hospedado no próprio domínio da aplicação (assetlinks.json no Android, apple-app-site-association no iOS). Se qualquer lado estiver faltando ou inconsistente, o link abre no navegador como fallback. Isso impede que um aplicativo malicioso reivindique seu domínio e intercepte seus links. Vamos montar esses arquivos em detalhes no Post 5.
Na prática: use custom schemes durante o desenvolvimento e migre para HTTPS (App Links / Universal Links) em produção.
Nossa Aplicação: FitConnect
Para tornar o aprendizado concreto, vamos construir o FitConnect — uma plataforma fictícia que conecta personal trainers com clientes por meio de um sistema de indicação.
O cenário:
Maria é personal trainer e quer indicar o app para seus alunos. Ela compartilha seu link de indicação. Quando um aluno se cadastra usando o link, Maria ganha bônus e o aluno ganha desconto.
O deep link principal da série:
https://fitconnect.app/signup?referralCode=TRAINER12345678901234
Quando um aluno clica nesse link, o app deve abrir diretamente na tela de cadastro com o código TRAINER12345678901234 já preenchido. Simples de descrever, mas com bastante detalhe de implementação por baixo.
-
Domínio:
fitconnect.app -
Custom scheme:
fitconnect:// -
Package:
com.fitconnect.app
Preparando a Estrutura
Antes de escrever qualquer código nativo, vale a pena definir as constantes, tipos e modelos que vão ser compartilhados entre todas as camadas do app.
Constantes
Centralizar strings como os nomes dos channels evita erros de digitação difíceis de rastrear. Se o nome do channel no Dart não bater exatamente com o do Kotlin ou Swift, a comunicação falha silenciosamente.
// lib/shared/const/deep_link_const.dart
class DeepLinkConst {
static const String methodChannel = 'com.fitconnect.app/deeplink';
static const String eventChannel = 'com.fitconnect.app/deeplink_stream';
static const String customScheme = 'fitconnect';
static const String httpsScheme = 'https';
static const String appHost = 'fitconnect.app';
static const String signupPath = '/signup';
static const String referralCodeParam = 'referralCode';
static const int referralCodeLength = 20;
}
Enum de Tipos
O getter isSecure torna o código mais expressivo — em vez de comparar strings ou verificar o scheme manualmente, você simplesmente checa data.type.isSecure.
// lib/shared/enums/deep_link_type.dart
enum DeepLinkType {
customScheme,
appLink, // Android HTTPS
universalLink, // iOS HTTPS
unknown;
bool get isSecure => this == appLink || this == universalLink;
}
Modelo de Dados (Freezed)
Se preferir, você não precisa utilizar o Freezed e pode criar seus modelos na mão. A grande vantagem de adotá-lo é a garantia de imutabilidade e a geração automática de código para comparação de objetos e o método copyWith. Como os deep links carregam parâmetros importantes (como o código de indicação), tratar esses dados de forma imutável nos protege de bugs difíceis de rastrear na navegação do app.
// lib/shared/models/deep_link_data.dart
@freezed
class DeepLinkData with _$DeepLinkData {
const factory DeepLinkData({
required String url,
required DeepLinkType type,
required String scheme,
String? host,
String? path,
@Default({}) Map<String, String> queryParameters,
String? referralCode,
required DateTime receivedAt,
}) = _DeepLinkData;
}
O que construímos até aqui
Ao final desta etapa, você já tem:
- Clareza sobre como deep links funcionam.
- Entendimento das diferenças entre os tipos de links.
- Uma base sólida no Flutter para começar a implementação.
Essa base será utilizada nos próximos passos para integrar o código nativo e completar o fluxo ponta a ponta.
Na próxima etapa, vamos partir para a implementação nativa no Android — incluindo AndroidManifest.xml e MainActivity.kt completos.
Código completo disponível no repositório: FitConnect no GitHub
Este é o primeiro post de uma série de 9 sobre deep links em Flutter. Nos próximos artigos, vamos do código nativo para o Android e iOS até deferred links, redirect pages e testes — tudo sem depender de pacotes prontos. Se você tem dúvidas, sugestões ou já passou por algum problema parecido com deep links, conta nos comentários! Adoro saber como essa experiência se aplica em projetos reais. E se quiser acompanhar os próximos posts, é só seguir aqui no Medium.
Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.
E você, já implementou deep links no seu app?
Qual foi o maior desafio que encontrou?
Quero usar esses casos reais nos próximos posts da série 👇

Top comments (0)