<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cristian Dornelles</title>
    <description>The latest articles on DEV Community by Cristian Dornelles (@cdornelles).</description>
    <link>https://dev.to/cdornelles</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F197395%2F68925fb8-bf0e-4b22-92cd-72f65f9ad21d.jpg</url>
      <title>DEV Community: Cristian Dornelles</title>
      <link>https://dev.to/cdornelles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cdornelles"/>
    <language>en</language>
    <item>
      <title>Deferred Deep Links: O Clique Aconteceu. A Instalação Também. E Agora? (Parte 6)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Sun, 26 Apr 2026 15:46:12 +0000</pubDate>
      <link>https://dev.to/cdornelles/deferred-deep-links-o-clique-aconteceu-a-instalacao-tambem-e-agora-parte-6-1ag7</link>
      <guid>https://dev.to/cdornelles/deferred-deep-links-o-clique-aconteceu-a-instalacao-tambem-e-agora-parte-6-1ag7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1hcuribki1xfmqfc5f4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1hcuribki1xfmqfc5f4.png" alt="Header" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;DeepLinkService&lt;/code&gt;, &lt;code&gt;MethodChannel&lt;/code&gt; e &lt;code&gt;EventChannel&lt;/code&gt; (Post 4), e publicamos os arquivos de verificação em &lt;code&gt;deeplinkslab.dev&lt;/code&gt; para que App Links e Universal Links funcionem em produção (Post 5).&lt;/p&gt;

&lt;p&gt;O link funciona. Mas só quando o app está instalado.&lt;/p&gt;




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

&lt;p&gt;O que acontece com o &lt;code&gt;referralCode&lt;/code&gt; enquanto ele passa pela loja, faz o download e abre o app pela primeira vez?&lt;/p&gt;

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

&lt;p&gt;É exatamente esse problema que os &lt;strong&gt;Deferred Deep Links&lt;/strong&gt; resolvem.&lt;/p&gt;

&lt;p&gt;Este é o sexto conteúdo de uma série completa sobre Deep Links no Flutter. Se você ainda não viu os posts anteriores: &lt;a href="https://medium.com/p/d56ea3619192?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 1 — Guia para Iniciantes&lt;/a&gt; | &lt;a href="https://medium.com/p/562bb353b3b2?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 2 — Android com Kotlin&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/deep-links-no-ios-implementa%C3%A7%C3%A3o-nativa-com-swift-flutter-parte-3-d37569fd0a15" rel="noopener noreferrer"&gt;Post 3 — iOS com Swift&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/conectando-tudo-integra%C3%A7%C3%A3o-flutter-com-deep-links-nativos-parte-4-b9b23c6e32e5" rel="noopener noreferrer"&gt;Post 4 — Integração Flutter&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/app-links-e-universal-links-deep-links-em-produ%C3%A7%C3%A3o-parte-5-73ed0a186e75" rel="noopener noreferrer"&gt;Post 5 — Produção&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Neste artigo você vai aprender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Por que deep links normais falham quando o app não está instalado.&lt;/li&gt;
&lt;li&gt;Solução simples com LocalStorage.&lt;/li&gt;
&lt;li&gt;Solução robusta com backend e fingerprint.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O desafio
&lt;/h2&gt;

&lt;p&gt;O fluxo do problema é direto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Usuário clica no link — app não está instalado.&lt;/li&gt;
&lt;li&gt;Navegador abre a loja (Play Store / App Store).&lt;/li&gt;
&lt;li&gt;Usuário instala o app e abre pela primeira vez.&lt;/li&gt;
&lt;li&gt;❓ Como o app sabe qual era o link original?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;A resposta para esse problema é &lt;strong&gt;Deferred Deep Links&lt;/strong&gt;: uma estratégia para preservar o contexto do link entre o clique e o primeiro launch do app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluxo do problema
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwi7us19mj0u5npvx9li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwi7us19mj0u5npvx9li.png" alt="Fluxo do problema: do clique à instalação" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Solução 1: LocalStorage
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;referralCode&lt;/code&gt; no &lt;code&gt;localStorage&lt;/code&gt; do navegador. Na primeira abertura do app, o Flutter verifica se existe um estado pendente salvo localmente e repassa o dado ao Flutter.&lt;/p&gt;

&lt;p&gt;No Flutter, a lógica fica no &lt;code&gt;DeepLinkService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/services/deep_link_service.dart&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeepLinkService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Salvar código após primeira abertura&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processDeferredLink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialLink&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;initialLink&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'referralCode'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_extractReferralCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialLink&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'pending_referral'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Recuperar código no signup&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getPendingReferralCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'pending_referral'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'pending_referral'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Cleanup após uso&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A integração acontece em dois momentos distintos do ciclo de vida do app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SplashScreen — primeiro launch&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;deepLinkService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processDeferredLink&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// SignupScreen — ao carregar a tela&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;pendingCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;deepLinkService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPendingReferralCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;referralCodeController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pendingCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;remove&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vantagens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Simples de implementar.&lt;/li&gt;
&lt;li&gt;✅ Funciona sem conexão.&lt;/li&gt;
&lt;li&gt;✅ Sem dependência de backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitações:&lt;/strong&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Solução 2: Backend com fingerprint
&lt;/h2&gt;

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

&lt;p&gt;O conceito central é o &lt;strong&gt;fingerprint&lt;/strong&gt;: 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.&lt;/p&gt;

&lt;p&gt;O fluxo no backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Backend: Criar short link&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/referral/create-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;referralCode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shortCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateShortCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;shortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://deeplinkslab.dev/s/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Backend: Processar clique&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/s/:code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referralCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// IP + User-Agent + Accept-Language&lt;/span&gt;

  &lt;span class="c1"&gt;// Salvar em cache por 48h&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pending:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;172800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Redirecionar para a loja da plataforma&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detectPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getStoreUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Backend: Recuperar código no primeiro launch&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/referral/get-pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pending:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pending:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Cleanup após uso&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Flutter, o primeiro launch faz uma chamada ao backend para verificar se há um código pendente associado ao fingerprint do device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/services/deep_link_service.dart&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;checkPendingOnBackend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/api/referral/get-pending'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'referralCode'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Funciona cross-device.&lt;/li&gt;
&lt;li&gt;✅ Analytics completo de cliques e conversões.&lt;/li&gt;
&lt;li&gt;✅ Mais robusto no mesmo device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Desvantagens:&lt;/strong&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Quando usar cada uma
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbdktjv3gpw83vta741e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbdktjv3gpw83vta741e.png" alt="Quando usar: LocalStorage vs Backend com fingerprint" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Você implementaria LocalStorage ou backend com fingerprint?&lt;br&gt;
Ou usaria um serviço pronto como AppsFlyer / Branch / Firebase?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: Flutter, Deep Links, Deferred Deep Links, Mobile Development&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>App Links e Universal Links: Deep Links em Produção (Parte 5)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Wed, 22 Apr 2026 01:21:58 +0000</pubDate>
      <link>https://dev.to/cdornelles/app-links-e-universal-links-deep-links-em-producao-parte-5-36gj</link>
      <guid>https://dev.to/cdornelles/app-links-e-universal-links-deep-links-em-producao-parte-5-36gj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6q4pjuiqh8sjvmxj5m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6q4pjuiqh8sjvmxj5m0.png" alt="Header"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Custom schemes funcionam bem para desenvolvimento, mas para produção você precisa de HTTPS. É aqui que entram App Links no Android e Universal Links no iOS — e onde a maioria dos problemas reais aparece.&lt;/p&gt;

&lt;p&gt;Este é o quinto conteúdo de uma série completa sobre Deep Links no Flutter. Se você ainda não viu os posts anteriores: &lt;a href="https://medium.com/p/d56ea3619192?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 1&lt;/a&gt; | &lt;a href="https://medium.com/p/562bb353b3b2?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 2&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/deep-links-no-ios-implementa%C3%A7%C3%A3o-nativa-com-swift-flutter-parte-3-d37569fd0a15" rel="noopener noreferrer"&gt;Post 3&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/conectando-tudo-integra%C3%A7%C3%A3o-flutter-com-deep-links-nativos-parte-4-b9b23c6e32e5" rel="noopener noreferrer"&gt;Post 4&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O problema com custom schemes
&lt;/h2&gt;

&lt;p&gt;Até aqui usamos &lt;code&gt;fitconnect://&lt;/code&gt; para testes. Funciona, mas em produção esse scheme tem limitações sérias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fitconnect://fitconnect.app/signup?referralCode=TRAINER1234567890123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Qualquer app pode registrar o mesmo scheme — sem nenhuma verificação.&lt;/li&gt;
&lt;li&gt;Se o usuário não tem o app instalado, o sistema exibe um erro genérico.&lt;/li&gt;
&lt;li&gt;Não há como garantir que o link chegue ao app correto.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://deeplinkslab.dev/signup?referralCode=TRAINER1234567890123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Verificação server-side: o sistema operacional confirma com o servidor antes de abrir o app.&lt;/li&gt;
&lt;li&gt;✅ Se o app não estiver instalado, o link abre no navegador como fallback natural.&lt;/li&gt;
&lt;li&gt;✅ Profissional e seguro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A diferença não é só cosmética. É uma questão de confiança — tanto do sistema quanto do usuário.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do domínio fictício ao domínio real
&lt;/h2&gt;

&lt;p&gt;Até aqui, usamos &lt;code&gt;fitconnect.app&lt;/code&gt; como domínio fictício para focar na implementação.&lt;/p&gt;

&lt;p&gt;Mas agora entramos na parte onde deep links deixam de ser apenas código — e passam a depender de infraestrutura real.&lt;/p&gt;

&lt;p&gt;App Links e Universal Links só funcionam com um domínio válido, acessível publicamente e com verificação bidirecional.&lt;/p&gt;

&lt;p&gt;Para isso, vamos usar um domínio real dedicado a testes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;deeplinkslab.dev&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A partir deste ponto, tudo que implementarmos será o mesmo que você precisa em produção.&lt;/p&gt;

&lt;p&gt;Você não precisa usar este domínio — pode (e deveria) usar o seu próprio. Um domínio &lt;code&gt;.dev&lt;/code&gt; custa em torno de R$ 60–80 por ano e já vem com HTTPS obrigatório, o que elimina uma configuração a menos. Qualquer registrador funciona: Registro.br, Namecheap, Cloudflare Registrar ou Squarespace Domains.&lt;/p&gt;

&lt;p&gt;Se quiser um guia específico — do registro do domínio até o deploy no Cloudflare Pages com os arquivos de verificação prontos — me avisa nos comentários. Se tiver demanda, faço um post dedicado só sobre isso.&lt;/p&gt;




&lt;h2&gt;
  
  
  Android: configurando o assetlinks.json
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;&lt;code&gt;assetlinks.json&lt;/code&gt;&lt;/strong&gt; é o arquivo que o Android baixa do seu servidor para confirmar: "esse app tem permissão para tratar links desse domínio?". Sem ele, o &lt;code&gt;android:autoVerify="true"&lt;/code&gt; que adicionamos no Post 2 não tem como concluir a verificação — e o link pode abrir um seletor em vez do app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtendo o SHA256 do certificado
&lt;/h3&gt;

&lt;p&gt;O arquivo &lt;code&gt;assetlinks.json&lt;/code&gt; precisa conter o SHA256 do certificado com que o app foi assinado. Para o keystore de debug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;keytool &lt;span class="nt"&gt;-list&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keystore&lt;/span&gt; ~/.android/debug.keystore &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-alias&lt;/span&gt; androiddebugkey &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-storepass&lt;/span&gt; android &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keypass&lt;/span&gt; android | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"SHA256:"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para o keystore de release, substitua o caminho e as credenciais pelo seu keystore de produção. &lt;strong&gt;Debug e release geram SHA256 diferentes&lt;/strong&gt; — o arquivo de produção precisa conter o fingerprint do certificado de release. Durante o desenvolvimento, o fingerprint de debug é suficiente para testar localmente.&lt;/p&gt;

&lt;p&gt;Se você usa o Google Play App Signing, o SHA256 correto para produção está em: &lt;strong&gt;Play Console → Configuração → Integridade do app → Certificado de assinatura do app&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O arquivo assetlinks.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delegate_permission/common.handle_all_urls"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"android_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.fitconnect.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sha256_cert_fingerprints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"SEU_SHA256"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A relação &lt;strong&gt;&lt;code&gt;delegate_permission/common.handle_all_urls&lt;/code&gt;&lt;/strong&gt; é o valor padrão e obrigatório — informa ao Android que o app tem permissão para tratar qualquer URL do domínio declarado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;

&lt;p&gt;O arquivo deve estar acessível publicamente em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;https://deeplinkslab.dev/.well-known/assetlinks.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dois requisitos que costumam causar problemas em produção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O servidor precisa responder com &lt;code&gt;Content-Type: application/json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O arquivo deve ser acessível via HTTPS sem redirecionamentos — o Android não segue redirects ao verificar o arquivo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se qualquer um desses requisitos falhar, o Android simplesmente ignora o arquivo — e o link passa a abrir no navegador sem nenhum erro visível.&lt;/p&gt;




&lt;h2&gt;
  
  
  iOS: configurando o apple-app-site-association
&lt;/h2&gt;

&lt;p&gt;No iOS, o equivalente é o &lt;strong&gt;&lt;code&gt;apple-app-site-association&lt;/code&gt;&lt;/strong&gt; (sem extensão). O iOS pode manter esse arquivo em cache.&lt;/p&gt;

&lt;p&gt;Se algo não funcionar durante o desenvolvimento, reinstalar o app geralmente força uma nova verificação.&lt;/p&gt;

&lt;h3&gt;
  
  
  O arquivo apple-app-site-association
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"applinks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"appID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TEAM_ID.com.fitconnect.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/signup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/signup/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O campo &lt;code&gt;apps&lt;/code&gt; deve ser um array vazio — é uma exigência da Apple, mesmo que pareça redundante. O &lt;code&gt;appID&lt;/code&gt; combina o Team ID com o bundle ID. O array &lt;code&gt;paths&lt;/code&gt; lista os caminhos que o app está autorizado a tratar; &lt;code&gt;/signup/*&lt;/code&gt; cobre rotas com subpaths ou query strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtendo o Team ID
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;a href="https://developer.apple.com/account" rel="noopener noreferrer"&gt;developer.apple.com/account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Vá em &lt;strong&gt;Membership&lt;/strong&gt; → o Team ID aparece na lista de informações da conta.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;https://deeplinkslab.dev/.well-known/apple-app-site-association&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O arquivo não deve ter extensão. Sirva-o com &lt;code&gt;Content-Type: application/json&lt;/code&gt; e sem redirecionamentos — os mesmos requisitos do Android. O iOS 14+ usa uma CDN da Apple para cachear o arquivo, por isso mudanças podem demorar a ser refletidas; reinstalar o app força a re-verificação durante o desenvolvimento.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que é essa verificação bidirecional?
&lt;/h2&gt;

&lt;p&gt;A lógica é a mesma nas duas plataformas: o sistema operacional só abre o app se as duas pontas se reconhecerem.&lt;/p&gt;

&lt;p&gt;O app declara, no &lt;code&gt;AndroidManifest.xml&lt;/code&gt; e no &lt;code&gt;Runner.entitlements&lt;/code&gt;, quais domínios ele se propõe a tratar. O servidor, por sua vez, confirma no &lt;code&gt;assetlinks.json&lt;/code&gt; e no &lt;code&gt;apple-app-site-association&lt;/code&gt; quais aplicativos têm permissão para tratar URLs do domínio.&lt;/p&gt;

&lt;p&gt;Se qualquer lado estiver faltando ou inconsistente — fingerprint errado, arquivo inacessível, caminho não listado — o link abre no navegador como fallback, sem mensagem de erro para o usuário. Isso protege contra um vetor real: sem verificação bidirecional, um aplicativo malicioso poderia registrar o mesmo scheme ou domínio e interceptar links destinados ao seu app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validando antes de fazer deploy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Android: Google Digital Asset Links
&lt;/h3&gt;

&lt;p&gt;A ferramenta oficial do Google valida o &lt;code&gt;assetlinks.json&lt;/code&gt; sem precisar instalar o app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://developers.google.com/digital-asset-links/tools/generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Informe o domínio e o package name — a ferramenta mostra se o arquivo está acessível, se o JSON é válido e se o fingerprint está correto.&lt;/p&gt;

&lt;p&gt;Também é possível verificar via &lt;code&gt;adb&lt;/code&gt; em um device com o app instalado. No Android 12+, o fluxo recomendado é resetar o estado, forçar a reverificação e só então consultar o resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Resetar o estado de verificação&lt;/span&gt;
adb shell pm set-app-links &lt;span class="nt"&gt;--package&lt;/span&gt; com.fitconnect.app 0 all

&lt;span class="c"&gt;# Forçar reverificação&lt;/span&gt;
adb shell pm verify-app-links &lt;span class="nt"&gt;--re-verify&lt;/span&gt; com.fitconnect.app

&lt;span class="c"&gt;# Consultar o resultado&lt;/span&gt;
adb shell pm get-app-links com.fitconnect.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O status &lt;code&gt;verified&lt;/code&gt; confirma que a verificação foi concluída com sucesso. Pular o reset pode fazer o comando retornar um estado antigo em cache — não necessariamente o resultado da configuração atual.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS: inspecionando o arquivo diretamente
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://deeplinkslab.dev/.well-known/apple-app-site-association
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o &lt;code&gt;curl&lt;/code&gt; retornar o JSON correto, o arquivo está acessível. Para validar o comportamento completo, a Apple recomenda testar em um device físico — o simulador não reproduz a verificação de Associated Domains com fidelidade.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checklist de produção
&lt;/h2&gt;

&lt;p&gt;Antes de fazer deploy, confirme cada item:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] SHA256 obtido para o keystore de debug (testes) e release (produção)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;assetlinks.json&lt;/code&gt; com package name e fingerprint corretos&lt;/li&gt;
&lt;li&gt;[ ] Arquivo acessível em &lt;code&gt;https://deeplinkslab.dev/.well-known/assetlinks.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Servidor respondendo com &lt;code&gt;Content-Type: application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Sem redirecionamentos no caminho &lt;code&gt;/.well-known/assetlinks.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Validado com a ferramenta Google Digital Asset Links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Team ID obtido no Apple Developer Portal&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;apple-app-site-association&lt;/code&gt; com appID e paths corretos&lt;/li&gt;
&lt;li&gt;[ ] Arquivo acessível em &lt;code&gt;https://deeplinkslab.dev/.well-known/apple-app-site-association&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;Associated Domains&lt;/code&gt; configurado no Xcode (&lt;code&gt;applinks:deeplinkslab.dev&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Testado em device físico (não apenas simulador)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;code&gt;assetlinks.json&lt;/code&gt; configurado com o SHA256 correto para Android.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;apple-app-site-association&lt;/code&gt; com Team ID e paths autorizados para iOS.&lt;/li&gt;
&lt;li&gt;Clareza sobre como funciona a verificação bidirecional e por que ela existe.&lt;/li&gt;
&lt;li&gt;Ferramentas e comandos para validar os dois arquivos antes de ir a produção.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este é o quinto de 9 posts da série. Com Android, iOS e Flutter integrados e a verificação de domínio no lugar, o fluxo básico de deep links está completo — mas ainda só funciona quando o usuário já tem o app instalado. No próximo post, resolvemos o caso mais difícil: o que acontece quando o usuário clica no link e ainda não tem o app.&lt;/p&gt;

&lt;p&gt;No próximo post: Deferred Deep Links — como salvar o contexto do link e recuperar após a instalação.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este artigo é uma crosspost do Medium — &lt;a href="https://medium.com/@crdornelles/app-links-e-universal-links-deep-links-em-produ%C3%A7%C3%A3o-parte-5-73ed0a186e75" rel="noopener noreferrer"&gt;leia lá também&lt;/a&gt; e deixa um clap se curtiu.&lt;/p&gt;

&lt;p&gt;Já subiu App Links em produção? Qual foi o erro mais difícil de debugar? Conta nos comentários.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Checkpoint da Série: o que já funciona, o que quebrou e para onde vamos (Parte 4.5)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:26:53 +0000</pubDate>
      <link>https://dev.to/cdornelles/checkpoint-da-serie-o-que-ja-funciona-o-que-quebrou-e-para-onde-vamos-parte-45-52nf</link>
      <guid>https://dev.to/cdornelles/checkpoint-da-serie-o-que-ja-funciona-o-que-quebrou-e-para-onde-vamos-parte-45-52nf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ulucega5s77zfl8z9sz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ulucega5s77zfl8z9sz.png" alt="Header" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quatro posts, três plataformas, um bug encontrado em produção.&lt;/p&gt;

&lt;p&gt;Antes de seguir para App Links e Universal Links no ar de verdade — com domínio, verificação bidirecional e os problemas que só aparecem fora do emulador — vale um respiro para ver o que já existe e o que ainda falta.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Post 1 — A fundação conceitual
&lt;/h3&gt;

&lt;p&gt;Tipos de deep link, diferenças entre custom scheme, App Links e Universal Links, e a estrutura base em Dart. Nada de pacote externo — só entender o problema antes de escrever código.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@crdornelles/deep-links-em-flutter-o-guia-definitivo-para-iniciantes-parte-1-d56ea3619192" rel="noopener noreferrer"&gt;Parte 1 — O Guia para Iniciantes&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 2 — Android nativo
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;AndroidManifest.xml&lt;/code&gt; com &lt;code&gt;intent-filter&lt;/code&gt; para custom scheme e HTTPS. &lt;code&gt;MainActivity.kt&lt;/code&gt; registrando os canais nativos — &lt;code&gt;MethodChannel&lt;/code&gt; para cold start, &lt;code&gt;EventChannel&lt;/code&gt; para app em execução.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/p/562bb353b3b2?postPublishedType=initial" rel="noopener noreferrer"&gt;Parte 2 — Android com Kotlin&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 3 — iOS nativo
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Info.plist&lt;/code&gt; com &lt;code&gt;CFBundleURLTypes&lt;/code&gt;, &lt;code&gt;Runner.entitlements&lt;/code&gt; com Associated Domains e &lt;code&gt;AppDelegate.swift&lt;/code&gt; espelhando a mesma lógica de canais do Android.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@crdornelles/deep-links-no-ios-implementa%C3%A7%C3%A3o-nativa-com-swift-flutter-parte-3-d37569fd0a15" rel="noopener noreferrer"&gt;Parte 3 — iOS com Swift&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 4 — Integração Flutter
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;DeepLinkService&lt;/code&gt; como ponto único de entrada: &lt;code&gt;MethodChannel&lt;/code&gt; busca o link inicial, &lt;code&gt;EventChannel&lt;/code&gt; recebe links com o app aberto, &lt;code&gt;StreamController.broadcast()&lt;/code&gt; distribui para quem estiver ouvindo. A &lt;code&gt;SignupPage&lt;/code&gt; pré-preenche o referral code e mostra um indicador visual quando o dado veio de um link.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@crdornelles/conectando-tudo-integra%C3%A7%C3%A3o-flutter-com-deep-links-nativos-parte-4-b9b23c6e32e5" rel="noopener noreferrer"&gt;Parte 4 — Integração Flutter&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  O bug que encontramos na prática
&lt;/h2&gt;

&lt;p&gt;Durante os testes do Post 4, o app abria normalmente mas o referral code nunca aparecia. Nenhum erro, nenhum log — o deep link simplesmente sumia.&lt;/p&gt;

&lt;p&gt;A causa: race condition com &lt;code&gt;StreamController.broadcast()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O problema na prática
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ como estava — evento perdido&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// link emitido aqui...&lt;/span&gt;
&lt;span class="n"&gt;_deepLinkSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepLinkStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// listener registrado tarde demais&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ como ficou — listener primeiro&lt;/span&gt;
&lt;span class="n"&gt;_deepLinkSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepLinkStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// ouvindo antes de qualquer evento&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// link emitido com listener ativo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;broadcast()&lt;/code&gt; não bufferiza. Se nenhum listener estiver ativo no momento em que o evento é emitido, ele se perde. A ordem importa.&lt;/p&gt;

&lt;p&gt;Esse é o tipo de bug que não aparece em log, não quebra o app, e passa despercebido até produção.&lt;/p&gt;




&lt;h2&gt;
  
  
  Onde estamos agora
&lt;/h2&gt;

&lt;p&gt;O fluxo ponta a ponta já funciona:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android&lt;/strong&gt; → captura o intent corretamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS&lt;/strong&gt; → captura a URL corretamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter&lt;/strong&gt; → recebe via &lt;code&gt;MethodChannel&lt;/code&gt; / &lt;code&gt;EventChannel&lt;/code&gt; e processa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI&lt;/strong&gt; → reage ao deep link com o referral code pré-preenchido&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9aryuswehzs26svybxti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9aryuswehzs26svybxti.png" alt="Fluxo ponta a ponta" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas ainda falta o mais difícil: fazer isso funcionar fora do ambiente de desenvolvimento.&lt;/p&gt;

&lt;p&gt;Código completo: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt; — branch &lt;code&gt;post/04-flutter&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Para onde vamos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Post 5 — Produção de verdade
&lt;/h3&gt;

&lt;p&gt;App Links no Android e Universal Links no iOS exigem verificação bidirecional de domínio. Sem isso, o link abre no navegador em vez do app. Vamos configurar o &lt;code&gt;assetlinks.json&lt;/code&gt; e o &lt;code&gt;apple-app-site-association&lt;/code&gt;, entender o que o sistema verifica e o que fazer quando a verificação falha.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 6 — Deferred Deep Links
&lt;/h3&gt;

&lt;p&gt;O usuário clica no link mas não tem o app instalado. Vai para a loja, instala, abre — e o referral code sumiu. O deferred deep link resolve isso. Vamos implementar com &lt;code&gt;LocalStorage&lt;/code&gt; no lado web e uma estratégia simples no backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 7 — Página de redirect inteligente
&lt;/h3&gt;

&lt;p&gt;Uma página HTML que detecta a plataforma, tenta abrir o app, e redireciona para a loja se não conseguir. O elo que conecta o link compartilhado ao app instalado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 8 — Testes e troubleshooting
&lt;/h3&gt;

&lt;p&gt;Checklist de deploy, como testar cada cenário (cold start, app aberto, link inválido), e os erros mais comuns com App Links e Universal Links — incluindo os que só aparecem depois do deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post 9 — Refatoração com Clean Architecture
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;DeepLinkHandler&lt;/code&gt; como alternativa ao &lt;code&gt;DeepLinkService&lt;/code&gt; atual — separando responsabilidades, facilitando testes e preparando o código para crescer sem virar bagunça.&lt;/p&gt;




&lt;p&gt;Se você chegou agora, os quatro primeiros posts constroem a fundação do zero. Se acompanhou desde o início, os próximos cinco levam para produção — com os problemas reais que aparecem quando o domínio precisa estar verificado e o usuário ainda não tem o app.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.&lt;/p&gt;

&lt;p&gt;E você, já implementou deep links no seu app?&lt;/p&gt;

&lt;p&gt;Qual foi o maior desafio que encontrou?&lt;/p&gt;

&lt;p&gt;Quero usar esses casos reais nos próximos posts da série 👇&lt;/p&gt;

&lt;p&gt;Tags: Flutter, Dart, MethodChannel, Deep Links, Mobile Development&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Conectando Tudo: Integração Flutter com Deep Links Nativos (Parte 4)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Sat, 11 Apr 2026 14:23:51 +0000</pubDate>
      <link>https://dev.to/cdornelles/conectando-tudo-integracao-flutter-com-deep-links-nativos-parte-4-5blb</link>
      <guid>https://dev.to/cdornelles/conectando-tudo-integracao-flutter-com-deep-links-nativos-parte-4-5blb</guid>
      <description>&lt;p&gt;Se você já tentou integrar deep links no Flutter usando código nativo, provavelmente passou por isso:&lt;/p&gt;

&lt;p&gt;o Android recebe o link…&lt;br&gt;&lt;br&gt;
o iOS também…&lt;/p&gt;

&lt;p&gt;mas o Flutter simplesmente não reage.&lt;/p&gt;

&lt;p&gt;Ou pior:&lt;br&gt;
funciona no cold start… mas não funciona com o app aberto.&lt;/p&gt;

&lt;p&gt;É exatamente esse problema que vamos resolver agora.&lt;/p&gt;

&lt;p&gt;Dando continuidade à série sobre Deep Links no Flutter, com novos artigos saindo semanalmente, ou quase. Se você ainda não viu os posts anteriores: &lt;a href="https://medium.com/p/d56ea3619192?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 1 — Guia para Iniciantes&lt;/a&gt; | &lt;a href="https://medium.com/p/562bb353b3b2?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 2 — Android com Kotlin&lt;/a&gt; | &lt;a href="https://medium.com/@crdornelles/deep-links-no-ios-implementa%C3%A7%C3%A3o-nativa-com-swift-flutter-parte-3-d37569fd0a15" rel="noopener noreferrer"&gt;Post 3 — iOS com Swift&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;Neste artigo, você vai aprender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como implementar DeepLinkService com MethodChannel e EventChannel no Flutter.&lt;/li&gt;
&lt;li&gt;Por que usar StreamController.broadcast() para eventos de deep link.&lt;/li&gt;
&lt;li&gt;Como pré-preencher a UI automaticamente a partir de um link.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  DeepLinkService no Flutter
&lt;/h2&gt;

&lt;p&gt;A solução é ter um ponto único de entrada para todos os deep links no app — o &lt;code&gt;DeepLinkService&lt;/code&gt;. Ele fala com o código nativo em dois momentos distintos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Via &lt;strong&gt;&lt;code&gt;MethodChannel&lt;/code&gt;&lt;/strong&gt;: quando o app abre e precisamos saber se havia um link pendente (app estava fechado).&lt;/li&gt;
&lt;li&gt;Via &lt;strong&gt;&lt;code&gt;EventChannel&lt;/code&gt;&lt;/strong&gt;: stream contínuo para links recebidos enquanto o app já está em execução.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/services/deep_link_service.dart&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:async'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:io'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/services.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeepLinkService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;DeepLinkService&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeepLinkService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_internal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;DeepLinkService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;DeepLinkService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_internal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;MethodChannel&lt;/span&gt; &lt;span class="n"&gt;_methodChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'com.fitconnect.app/deeplink'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;EventChannel&lt;/span&gt; &lt;span class="n"&gt;_eventChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'com.fitconnect.app/deeplink_stream'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamController&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeepLinkData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;StreamController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeepLinkData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;deepLinkStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_initialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;_initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Busca deep link inicial (app estava fechado)&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;initialUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_methodChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'getInitialLink'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialUrl&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_parseDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Stream para links quando app está aberto&lt;/span&gt;
    &lt;span class="n"&gt;_eventChannel&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;receiveBroadcastStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_parseDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nl"&gt;onError:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Log ou tratamento&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nl"&gt;cancelOnError:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;DeepLinkData&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_parseDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeepLinkData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;url:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;type:&lt;/span&gt; &lt;span class="n"&gt;_determineType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;scheme:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;path:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;queryParameters:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queryParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;referralCode:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queryParameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'referralCode'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="nl"&gt;receivedAt:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;DeepLinkType&lt;/span&gt; &lt;span class="n"&gt;_determineType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'fitconnect'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'https'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'fitconnect.app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isIOS&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;universalLink&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;appLink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Como esse controller vive durante todo o ciclo de vida do app, não fazemos o close manual — mas em serviços descartáveis isso seria obrigatório.&lt;/p&gt;

&lt;p&gt;Esse padrão também facilita testes e evolução futura (ex: analytics, tracking, feature flags baseadas em deep link).&lt;/p&gt;

&lt;p&gt;Mas mais importante do que isso -&lt;/p&gt;

&lt;p&gt;na prática, o DeepLinkService funciona como uma camada de tradução:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fird2y3615q2pa4tq5tb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fird2y3615q2pa4tq5tb9.png" alt="Camada de tradução" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ele isola completamente o restante da aplicação da complexidade de cada plataforma.&lt;/p&gt;
&lt;h3&gt;
  
  
  Por que StreamController.broadcast()?
&lt;/h3&gt;

&lt;p&gt;Um &lt;code&gt;StreamController&lt;/code&gt; padrão só permite um listener. Se o app tiver múltiplas telas ouvindo deep links ao mesmo tempo — ou se o listener for registrado após o evento ser emitido — o evento se perde. O &lt;code&gt;.broadcast()&lt;/code&gt; resolve os dois problemas: aceita múltiplos listeners e não lança exceção se nenhum estiver ativo no momento do evento.&lt;/p&gt;
&lt;h3&gt;
  
  
  A responsabilidade de cada canal
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;_methodChannel&lt;/code&gt; faz uma chamada pontual — &lt;code&gt;getInitialLink&lt;/code&gt; — e retorna uma vez. É o suficiente para o cenário de cold start. Já o &lt;code&gt;_eventChannel&lt;/code&gt; permanece aberto como um stream; qualquer link recebido com o app em execução chega por ele. Juntos, cobrem todos os cenários sem sobreposição.&lt;/p&gt;
&lt;h3&gt;
  
  
  O método _parseDeepLink
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Uri.parse&lt;/code&gt; decompõe a URL em partes (&lt;code&gt;scheme&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;queryParameters&lt;/code&gt;) sem nenhuma dependência externa. A partir daí, montamos o &lt;code&gt;DeepLinkData&lt;/code&gt; com os campos que o app vai precisar — incluindo o &lt;code&gt;referralCode&lt;/code&gt; extraído diretamente dos query parameters. Se a URL for inválida, o método retorna &lt;code&gt;null&lt;/code&gt; e o link é ignorado silenciosamente.&lt;/p&gt;
&lt;h3&gt;
  
  
  Evitando processamento duplicado no Flutter
&lt;/h3&gt;

&lt;p&gt;Dependendo da plataforma, o mesmo deep link pode chegar mais de uma vez (especialmente em cold start).&lt;/p&gt;

&lt;p&gt;Uma estratégia simples é manter o último link processado e ignorar duplicados —&lt;br&gt;
principalmente em cenários onde o mesmo evento pode chegar via MethodChannel e EventChannel.&lt;/p&gt;


&lt;h2&gt;
  
  
  Integrando no ciclo de vida do app
&lt;/h2&gt;

&lt;p&gt;Com o &lt;code&gt;DeepLinkService&lt;/code&gt; pronto, a integração acontece no widget raiz do app.&lt;/p&gt;

&lt;p&gt;É aqui que conectamos o serviço ao ciclo de vida da aplicação.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/app_widget.dart&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_AppState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;StreamSubscription&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_deepLinkSub&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_initDeepLinks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_initDeepLinks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeepLinkService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Listener registrado antes de initialize() para não perder&lt;/span&gt;
    &lt;span class="c1"&gt;// o evento de cold start emitido durante a inicialização&lt;/span&gt;
    &lt;span class="n"&gt;_deepLinkSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepLinkStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'/signup'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;referralCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_handleSignupLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_handleSignupLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeepLinkData&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WidgetsBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addPostFrameCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pushNamed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/signup'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;milliseconds:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SignupController&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
      &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setReferralCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;referralCode&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_deepLinkSub&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Atenção: a ordem importa
&lt;/h3&gt;

&lt;p&gt;Repare que o listener é registrado &lt;strong&gt;antes&lt;/strong&gt; de chamar &lt;code&gt;initialize()&lt;/code&gt;. Isso não é acidente.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;StreamController.broadcast()&lt;/code&gt; não bufferiza eventos — se nenhum listener estiver ativo no momento em que o evento é emitido, ele se perde silenciosamente. E o &lt;code&gt;initialize()&lt;/code&gt; pode emitir o link inicial (cold start) ainda durante sua execução.&lt;/p&gt;

&lt;p&gt;Se você inverter a ordem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ errado — o evento de cold start é perdido&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// evento emitido aqui...&lt;/span&gt;
&lt;span class="n"&gt;_deepLinkSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepLinkStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// ...mas o listener só existe aqui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O app abre, o link chega no Android/iOS, mas o Flutter nunca reage. Nenhum erro, nenhum log — o deep link simplesmente some.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O uso de &lt;code&gt;Future.delayed&lt;/code&gt; aqui é uma simplificação didática. Em produção, o ideal é desacoplar navegação e estado — via state management (MobX, Bloc, etc.) ou via evento disparado após o build da tela. Isso elimina dependência de timing arbitrário e condições de corrida entre navegação e UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O &lt;code&gt;StreamSubscription&lt;/code&gt; precisa ser cancelado no &lt;code&gt;dispose&lt;/code&gt; para evitar memory leaks. Como o widget raiz raramente é removido da árvore, na prática isso raramente é acionado — mas é uma boa prática que vale manter.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI da SignupPage
&lt;/h2&gt;

&lt;p&gt;A última peça é a tela de cadastro. O campo de referral code precisa de uma decisão de UX clara: mostrar ao usuário que o dado veio de um link, mas permitir que ele edite se quiser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/signup/signup_page.dart&lt;/span&gt;

&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;referralCodeController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;labelText:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cameFromDeepLink&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;'Código de Indicação (via link)'&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Código de Indicação (opcional)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;suffixIcon:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cameFromDeepLink&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;green&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;maxLength:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;enabled:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Sempre editável&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O campo é sempre editável — mesmo quando pré-preenchido por deep link. O label e o ícone mudam para dar contexto ao usuário, mas nunca bloqueamos a edição. Travar o campo criaria fricção desnecessária: e se o usuário cometeu um erro no código de indicação ou quer usar outro?&lt;/p&gt;




&lt;h2&gt;
  
  
  Fluxo completo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6kfchz1aca0608uce4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6kfchz1aca0608uce4y.png" alt="Fluxo completo" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;A partir daqui, o deep link deixa de ser um detalhe técnico.&lt;/p&gt;

&lt;p&gt;Ele passa a ser parte da experiência do usuário.&lt;/p&gt;

&lt;p&gt;O app não apenas abre.&lt;/p&gt;

&lt;p&gt;Ele entende o contexto.&lt;/p&gt;




&lt;p&gt;No próximo post, vamos sair do ambiente de desenvolvimento e ir para produção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Links funcionando de verdade no Android&lt;/li&gt;
&lt;li&gt;Universal Links validados no iOS&lt;/li&gt;
&lt;li&gt;Verificação bidirecional com domínio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É aqui que começam os problemas reais de produção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;domínio não verificado corretamente&lt;/li&gt;
&lt;li&gt;link abrindo no navegador em vez do app&lt;/li&gt;
&lt;li&gt;comportamento inconsistente entre Android e iOS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E é exatamente isso que vamos resolver.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;code&gt;DeepLinkService&lt;/code&gt; como ponto único de entrada para deep links no Flutter.&lt;/li&gt;
&lt;li&gt;Integração com &lt;code&gt;MethodChannel&lt;/code&gt; (cold start) e &lt;code&gt;EventChannel&lt;/code&gt; (app em execução).&lt;/li&gt;
&lt;li&gt;Roteamento automático para a &lt;code&gt;SignupPage&lt;/code&gt; com o referral code pré-preenchido.&lt;/li&gt;
&lt;li&gt;A decisão de UX de manter o campo sempre editável, com indicador visual de origem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este é o quarto de 9 posts da série. Com Android, iOS e Flutter integrados, o fluxo ponta a ponta já funciona — mas ainda em ambiente de desenvolvimento. No próximo post, vamos colocar isso em produção: configurar App Links no Android e Universal Links no iOS com verificação bidirecional de domínio. Se você já teve dor de cabeça com essa etapa, conta nos comentários — vou usar isso no Post 5.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.&lt;/p&gt;

&lt;p&gt;E você, já implementou deep links no seu app?&lt;/p&gt;

&lt;p&gt;Qual foi o maior desafio que encontrou?&lt;/p&gt;

&lt;p&gt;Quero usar esses casos reais nos próximos posts da série 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: Flutter, Dart, MethodChannel, Deep Links, Mobile Development&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Deep Links no iOS: Implementação Nativa com Swift + Flutter (Parte 3)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Wed, 08 Apr 2026 12:38:20 +0000</pubDate>
      <link>https://dev.to/cdornelles/deep-links-no-ios-implementacao-nativa-com-swift-flutter-parte-3-1521</link>
      <guid>https://dev.to/cdornelles/deep-links-no-ios-implementacao-nativa-com-swift-flutter-parte-3-1521</guid>
      <description>&lt;p&gt;No post anterior, configuramos o Android para capturar deep links usando Kotlin. Agora é a vez do iOS — e você vai ver como a implementação em Swift pode ficar bastante limpa.&lt;/p&gt;

&lt;p&gt;Dando continuidade à série sobre Deep Links no Flutter, agora vamos implementar o fluxo nativo no iOS. Se você ainda não viu os posts anteriores, recomendo começar por aqui: &lt;a href="https://medium.com/p/d56ea3619192?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 1 — Guia para Iniciantes&lt;/a&gt; | &lt;a href="https://medium.com/p/562bb353b3b2?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 2 — Android com Kotlin&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Neste artigo, você vai aprender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como configurar Info.plist e Associated Domains para deep links.&lt;/li&gt;
&lt;li&gt;A diferença entre custom schemes e Universal Links no iOS.&lt;/li&gt;
&lt;li&gt;Como implementar AppDelegate.swift com FlutterMethodChannel e FlutterEventChannel.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configurando o Info.plist
&lt;/h2&gt;

&lt;p&gt;No iOS, o registro de um &lt;strong&gt;custom scheme&lt;/strong&gt; é feito no &lt;code&gt;Info.plist&lt;/code&gt;, por meio da chave &lt;code&gt;CFBundleURLTypes&lt;/code&gt;. É aqui que o sistema operacional aprende que links com o scheme &lt;code&gt;fitconnect://&lt;/code&gt; devem abrir este app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ios/Runner/Info.plist --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleURLTypes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleTypeRole&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Editor&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleURLName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.fitconnect.app&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleURLSchemes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;fitconnect&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CFBundleURLSchemes&lt;/code&gt; é o que de fato registra o scheme. O &lt;code&gt;CFBundleURLName&lt;/code&gt; é um identificador interno — por convenção, usa-se o bundle ID reverso. O &lt;code&gt;CFBundleTypeRole&lt;/code&gt; como &lt;code&gt;Editor&lt;/code&gt; indica que o app não apenas lê o tipo de URL, mas também pode criar links com esse scheme.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configurando Universal Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Universal Links&lt;/strong&gt; exigem um passo a mais: o app precisa declarar quais domínios ele está autorizado a tratar. Essa declaração vai no arquivo &lt;code&gt;Runner.entitlements&lt;/code&gt;, sob a chave &lt;code&gt;com.apple.developer.associated-domains&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ios/Runner/Runner.entitlements --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.developer.associated-domains&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;applinks:fitconnect.app&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O prefixo &lt;code&gt;applinks:&lt;/code&gt; é obrigatório — é ele que instrui o iOS a tratar o domínio como Universal Link. Sem ele, o sistema ignora a entrada. A verificação bidirecional (o arquivo &lt;code&gt;apple-app-site-association&lt;/code&gt; no servidor) será montada no Post 5.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementando o AppDelegate.swift
&lt;/h2&gt;

&lt;p&gt;Com a configuração pronta, o iOS entrega os links ao &lt;code&gt;AppDelegate&lt;/code&gt;. Diferente do Android, onde a entrada costuma ser centralizada em torno do Intent, no iOS o link pode chegar por caminhos diferentes: no cold start via &lt;code&gt;launchOptions&lt;/code&gt;, por custom schemes via &lt;code&gt;open url&lt;/code&gt; e por Universal Links via &lt;code&gt;continue userActivity&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Flutter&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UIKit&lt;/span&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppDelegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterAppDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;CHANNEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.fitconnect.app/deeplink"&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;EVENT_CHANNEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.fitconnect.app/deeplink_stream"&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;methodChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterMethodChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;eventChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterEventChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;eventSink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterEventSink&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;initialLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;lastProcessedLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;didFinishLaunchingWithOptions&lt;/span&gt; &lt;span class="nv"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;LaunchOptionsKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootViewController&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;FlutterViewController&lt;/span&gt;
        &lt;span class="nf"&gt;setupChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Captura deep link inicial (cold start)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;handleDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userActivityDictionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userActivityDictionary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AnyHashable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;userActivityDictionary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userActivity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;NSUserActivity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;userActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activityType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;NSUserActivityTypeBrowsingWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webpageURL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;handleDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kt"&gt;GeneratedPluginRegistrant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;didFinishLaunchingWithOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setupChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterViewController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// MethodChannel: Flutter pede o deep link inicial&lt;/span&gt;
        &lt;span class="n"&gt;methodChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FlutterMethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CHANNEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;methodChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setMethodCallHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"getInitialLink"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialLink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FlutterMethodNotImplemented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// EventChannel: stream de deep links em tempo real enquanto o app está em execução&lt;/span&gt;
        &lt;span class="n"&gt;eventChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FlutterEventChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EVENT_CHANNEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;eventChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Custom URL Scheme&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;open&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;OpenURLOptionsKey&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;handleDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Universal Links&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="nv"&gt;userActivity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSUserActivity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;restorationHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kt"&gt;UIUserActivityRestoring&lt;/span&gt;&lt;span class="p"&gt;]?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;userActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activityType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;NSUserActivityTypeBrowsingWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webpageURL&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;handleDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleDeepLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;urlString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absoluteString&lt;/span&gt;

        &lt;span class="c1"&gt;// Evita processar o mesmo link duas vezes&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;urlString&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;lastProcessedLink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;lastProcessedLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlString&lt;/span&gt;
        &lt;span class="kt"&gt;NSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Deep link: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;urlString&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isInitial&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;initialLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlString&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;eventSink&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="n"&gt;urlString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;AppDelegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FlutterStreamHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;onListen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;withArguments&lt;/span&gt; &lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
        &lt;span class="n"&gt;eventSink&lt;/span&gt; &lt;span class="nv"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="kt"&gt;FlutterEventSink&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;FlutterError&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventSink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withArguments&lt;/span&gt; &lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;FlutterError&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventSink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Por que dois métodos de entrada?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;open url&lt;/code&gt;: chamado quando o link usa um &lt;strong&gt;custom scheme&lt;/strong&gt; (&lt;code&gt;fitconnect://&lt;/code&gt;). O iOS já sabe que é para abrir o app porque o scheme foi registrado no &lt;code&gt;Info.plist&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;continue userActivity&lt;/code&gt;: chamado para &lt;strong&gt;Universal Links&lt;/strong&gt; (HTTPS). O iOS só chega aqui após confirmar que o domínio está no &lt;code&gt;Runner.entitlements&lt;/code&gt; e que o servidor possui o arquivo &lt;code&gt;apple-app-site-association&lt;/code&gt;. Se qualquer verificação falhar, o link abre no Safari como fallback — sem erros, sem seletor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcncnxfg00fa7ubsqi3cq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcncnxfg00fa7ubsqi3cq.png" alt="Diagrama de fluxo no iOS" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Por que o AppDelegate adota FlutterStreamHandler via extension?
&lt;/h3&gt;

&lt;p&gt;Em vez de criar uma classe separada para o stream handler, o próprio &lt;code&gt;AppDelegate&lt;/code&gt; implementa o protocolo &lt;code&gt;FlutterStreamHandler&lt;/code&gt; via extension. Isso mantém o código coeso: tudo relacionado à entrega de links fica no mesmo lugar. O &lt;code&gt;onListen&lt;/code&gt; guarda a referência para o &lt;code&gt;eventSink&lt;/code&gt;, e o &lt;code&gt;onCancel&lt;/code&gt; a limpa quando o Flutter para de ouvir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testando no iOS
&lt;/h2&gt;

&lt;p&gt;Para testar sem precisar de um device físico, o simulador do Xcode expõe o comando &lt;code&gt;xcrun simctl openurl&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Custom scheme — não requer verificação de domínio&lt;/span&gt;
xcrun simctl openurl booted &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"fitconnect://fitconnect.app/signup?referralCode=TRAINER1234567890123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Universal Link (HTTPS)&lt;/span&gt;
xcrun simctl openurl booted &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://fitconnect.app/signup?referralCode=TRAINER1234567890123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; testar Universal Links no iOS pode ser enganoso. Em muitos casos, abrir a URL diretamente no Safari não reproduz o comportamento real de associação com o app. Para validar corretamente, prefira tocar no link a partir de apps como &lt;strong&gt;Mail&lt;/strong&gt;, &lt;strong&gt;Notas&lt;/strong&gt; ou &lt;strong&gt;iMessage&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Android vs iOS: comparação rápida
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv96j97pd6h7rdfk0dldp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv96j97pd6h7rdfk0dldp.png" alt="Android vs iOS: comparação rápida" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lógica é a mesma nas duas plataformas — o que muda são as APIs e os arquivos de configuração. O &lt;code&gt;handleDeepLink&lt;/code&gt; no Swift é o equivalente direto do &lt;code&gt;handleIntent&lt;/code&gt; no Kotlin.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;code&gt;Info.plist&lt;/code&gt; configurado para custom schemes.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;Runner.entitlements&lt;/code&gt; com o domínio para Universal Links.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;AppDelegate.swift&lt;/code&gt; com &lt;code&gt;FlutterMethodChannel&lt;/code&gt; (link inicial) e &lt;code&gt;FlutterEventChannel&lt;/code&gt; (stream em tempo real).&lt;/li&gt;
&lt;li&gt;Clareza sobre a diferença entre &lt;code&gt;open url&lt;/code&gt; e &lt;code&gt;continue userActivity&lt;/code&gt; e quando cada um é chamado.&lt;/li&gt;
&lt;li&gt;Comandos &lt;code&gt;xcrun simctl&lt;/code&gt; prontos para testar no simulador.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este é o terceiro de 9 posts da série. Com Android e iOS prontos, temos o código nativo completo das duas plataformas. Se você já passou por algum problema com Universal Links — domínio não verificado, link abrindo no Safari em vez do app — conta nos comentários, esse tipo de detalhe vai direto para os próximos artigos.&lt;/p&gt;

&lt;p&gt;No próximo post, conectamos tudo no Flutter: o &lt;code&gt;DeepLinkService&lt;/code&gt; em Dart que fala com o código nativo via &lt;code&gt;MethodChannel&lt;/code&gt; e &lt;code&gt;EventChannel&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.&lt;/p&gt;

&lt;p&gt;E você, já implementou deep links no seu app?&lt;/p&gt;

&lt;p&gt;Qual foi o maior desafio que encontrou?&lt;/p&gt;

&lt;h2&gt;
  
  
  Quero usar esses casos reais nos próximos posts da série 👇
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Tags: Flutter, iOS, Swift, Universal Links, Deep Links&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Deep Links no Android: Implementação Nativa com Kotlin + Flutter (Parte 2)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Mon, 06 Apr 2026 13:58:31 +0000</pubDate>
      <link>https://dev.to/cdornelles/deep-links-no-android-implementacao-nativa-com-kotlin-flutter-parte-2-311n</link>
      <guid>https://dev.to/cdornelles/deep-links-no-android-implementacao-nativa-com-kotlin-flutter-parte-2-311n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3icyyd6kc77dypwtf17.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3icyyd6kc77dypwtf17.png" alt="Header" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No post anterior, preparamos a base — constantes, enum de tipos e modelo de dados. Agora é hora de sujar as mãos com código nativo. Vamos configurar o Android para capturar deep links e entregá-los ao Flutter — sem nenhum pacote externo no meio do caminho.&lt;/p&gt;

&lt;p&gt;Spoiler: é mais simples do que parece — mas alguns detalhes fazem toda a diferença na prática.&lt;/p&gt;

&lt;p&gt;Dando continuidade à série sobre Deep Links no Flutter, com novos artigos saindo semanalmente, ou quase. Se você ainda não viu a base do projeto em Flutter, recomendo começar pelo primeiro artigo da série: &lt;a href="https://medium.com/p/d56ea3619192?postPublishedType=initial" rel="noopener noreferrer"&gt;Post 1 — Guia para Iniciantes&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Neste artigo, você vai aprender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como configurar o AndroidManifest.xml para deep links.&lt;/li&gt;
&lt;li&gt;A diferença entre MethodChannel e EventChannel.&lt;/li&gt;
&lt;li&gt;Como testar com adb sem precisar de um dispositivo físico.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configurando o AndroidManifest.xml
&lt;/h2&gt;

&lt;p&gt;O Android descobre que seu app pode tratar um link por meio de &lt;strong&gt;intent-filters&lt;/strong&gt; declarados no &lt;code&gt;AndroidManifest.xml&lt;/code&gt;. Podemos declarar dois intent-filters separados (um para custom scheme e outro para HTTPS) ou um único filtro com múltiplos &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt;. Neste exemplo, vamos manter separados para maior clareza didática.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- android/app/src/main/AndroidManifest.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".MainActivity"&lt;/span&gt;
    &lt;span class="na"&gt;android:launchMode=&lt;/span&gt;&lt;span class="s"&gt;"singleTop"&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Intent filter padrão (launcher) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.MAIN"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.LAUNCHER"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Deep Link: Custom Scheme --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.VIEW"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.DEFAULT"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.BROWSABLE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:scheme=&lt;/span&gt;&lt;span class="s"&gt;"fitconnect"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:host=&lt;/span&gt;&lt;span class="s"&gt;"fitconnect.app"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Deep Link: HTTPS (App Links) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;intent-filter&lt;/span&gt; &lt;span class="na"&gt;android:autoVerify=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.VIEW"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.DEFAULT"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.BROWSABLE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:scheme=&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:host=&lt;/span&gt;&lt;span class="s"&gt;"fitconnect.app"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;android:pathPrefix=&lt;/span&gt;&lt;span class="s"&gt;"/signup"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/activity&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Três atributos merecem atenção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;android:launchMode="singleTop"&lt;/code&gt;&lt;/strong&gt;: evita criar uma nova instância da Activity quando ela já está no topo da stack, direcionando o novo intent para &lt;code&gt;onNewIntent&lt;/code&gt;. Sem isso, o usuário poderia acabar com múltiplas instâncias da mesma Activity na stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BROWSABLE&lt;/code&gt;&lt;/strong&gt;: autoriza que links externos — de um e-mail, mensagem ou navegador — possam abrir o app. Sem essa categoria, o intent-filter é invisível para o sistema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;android:autoVerify="true"&lt;/code&gt;&lt;/strong&gt;: ativa a verificação bidirecional do App Links. O Android vai checar se o servidor confirma que este app tem permissão para tratar o domínio. Montamos esse arquivo de verificação no Post 5.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Implementando o MainActivity.kt
&lt;/h2&gt;

&lt;p&gt;Com o manifest configurado, o Android vai entregar o link como um &lt;code&gt;Intent&lt;/code&gt; para a &lt;code&gt;MainActivity&lt;/code&gt;. Agora precisamos capturá-lo e repassar ao Flutter via dois canais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MethodChannel&lt;/code&gt;&lt;/strong&gt;: Flutter chama quando o app abre e precisa saber se havia um link pendente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EventChannel&lt;/code&gt;&lt;/strong&gt;: stream contínuo para links recebidos com o app já em execução.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.fitconnect.app&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.Intent&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.net.Uri&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.os.Bundle&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.util.Log&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.embedding.android.FlutterFragmentActivity&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.embedding.engine.FlutterEngine&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.plugin.common.EventChannel&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.plugin.common.MethodChannel&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FlutterFragmentActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;TAG&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"FitConnect"&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;CHANNEL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.fitconnect.app/deeplink"&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;EVENT_CHANNEL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.fitconnect.app/deeplink_stream"&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;methodChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;eventChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;eventSink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EventSink&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;initialLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;lastProcessedLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"onCreate - checking for deep link"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;handleIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isInitial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configureFlutterEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FlutterEngine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureFlutterEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// MethodChannel: Flutter pede deep link inicial&lt;/span&gt;
        &lt;span class="n"&gt;methodChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dartExecutor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;CHANNEL&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;methodChannel&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setMethodCallHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"getInitialLink"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialLink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notImplemented&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// EventChannel: Stream de deep links em tempo real&lt;/span&gt;
        &lt;span class="n"&gt;eventChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dartExecutor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;EVENT_CHANNEL&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;eventChannel&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setStreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StreamHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onListen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EventChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EventSink&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;eventSink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;eventSink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNewIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNewIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"onNewIntent - app was already open"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;handleIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isInitial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;handleIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_VIEW&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;deepLinkUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// Evita processar o mesmo link duas vezes&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deepLinkUrl&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;lastProcessedLink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

            &lt;span class="n"&gt;lastProcessedLink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deepLinkUrl&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Deep link: $deepLinkUrl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isInitial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// App estava fechado&lt;/span&gt;
                &lt;span class="n"&gt;initialLink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deepLinkUrl&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// App estava aberto — envia via stream&lt;/span&gt;
                &lt;span class="n"&gt;eventSink&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deepLinkUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que acontece em cada cenário
&lt;/h3&gt;

&lt;p&gt;O fluxo de entrega do link depende de como o app foi aberto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed8p07ckgve6bb2inczx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed8p07ckgve6bb2inczx.png" alt="Diagrama de fluxo no Android" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;lastProcessedLink&lt;/code&gt; existe para um caso específico: em algumas versões do Android, &lt;code&gt;onCreate&lt;/code&gt; e &lt;code&gt;onNewIntent&lt;/code&gt; podem ser chamados em sequência para o mesmo link. Sem essa proteção, o Flutter processaria o mesmo deep link duas vezes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testando com adb
&lt;/h2&gt;

&lt;p&gt;Antes de integrar com o Flutter, vale confirmar que o Android está capturando os links corretamente. O &lt;code&gt;adb&lt;/code&gt; permite simular cliques em links diretamente pelo terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Testa o custom scheme (fitconnect://)&lt;/span&gt;
&lt;span class="c"&gt;# Deve abrir o app sem precisar de verificação de domínio&lt;/span&gt;
adb shell am start &lt;span class="nt"&gt;-a&lt;/span&gt; android.intent.action.VIEW &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"fitconnect://fitconnect.app/signup?referralCode=TRAINER1234567890123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Testa o App Link (HTTPS)&lt;/span&gt;
&lt;span class="c"&gt;# Pode abrir um seletor de aplicativos se a verificação do domínio ainda não estiver concluída&lt;/span&gt;
adb shell am start &lt;span class="nt"&gt;-a&lt;/span&gt; android.intent.action.VIEW &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"https://fitconnect.app/signup?referralCode=TRAINER1234567890123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o custom scheme abrir o app, o manifest está correto. Se o HTTPS mostrar um seletor de aplicativos em vez de abrir direto — é sinal de que a verificação bidirecional ainda não está configurada. Isso é esperado neste ponto da série e será resolvido no Post 5.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O AndroidManifest configurado com os dois intent-filters: custom scheme e App Links.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;MainActivity.kt&lt;/code&gt; com &lt;code&gt;MethodChannel&lt;/code&gt; (link inicial) e &lt;code&gt;EventChannel&lt;/code&gt; (stream em tempo real).&lt;/li&gt;
&lt;li&gt;Clareza sobre a diferença entre &lt;code&gt;onCreate&lt;/code&gt; e &lt;code&gt;onNewIntent&lt;/code&gt; e por que ambos precisam ser tratados.&lt;/li&gt;
&lt;li&gt;Comandos &lt;code&gt;adb&lt;/code&gt; prontos para testar sem precisar de dispositivo físico.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este é o segundo de 9 posts da série. Se você já passou por alguma situação estranha com intent-filters no Android, conta nos comentários — esse tipo de detalhe vai direto para os próximos artigos.&lt;/p&gt;

&lt;p&gt;Na próxima etapa, vamos replicar esse fluxo no iOS usando Swift — e você vai perceber como os conceitos são os mesmos, mesmo com APIs diferentes.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.&lt;/p&gt;

&lt;p&gt;E você, já implementou deep links no seu app?&lt;/p&gt;

&lt;p&gt;Qual foi o maior desafio que encontrou?&lt;/p&gt;

&lt;h2&gt;
  
  
  Quero usar esses casos reais nos próximos posts da série 👇
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Tags: Flutter, Android, Kotlin, Deep Links, Mobile Development&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Deep Links em Flutter: O Guia Definitivo para Iniciantes (Sem Pacotes de Terceiros) (Parte 1)</title>
      <dc:creator>Cristian Dornelles</dc:creator>
      <pubDate>Sat, 04 Apr 2026 18:59:27 +0000</pubDate>
      <link>https://dev.to/cdornelles/deep-links-em-flutter-o-guia-definitivo-para-iniciantes-sem-pacotes-de-terceiros-parte-1-4a31</link>
      <guid>https://dev.to/cdornelles/deep-links-em-flutter-o-guia-definitivo-para-iniciantes-sem-pacotes-de-terceiros-parte-1-4a31</guid>
      <description>&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;Neste artigo, você vai aprender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O que são deep links.&lt;/li&gt;
&lt;li&gt;Diferença entre App Links e Custom Schemes.&lt;/li&gt;
&lt;li&gt;Como estruturar a base no Flutter.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O que são Deep Links?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Deep links&lt;/em&gt; são URLs que levam o usuário diretamente para uma tela específica do seu app, em vez de abrir no navegador.&lt;/p&gt;

&lt;p&gt;Exemplo real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fluxo tradicional&lt;/strong&gt;: Abre navegador → usuário instala o app → abre → navega até a tela.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep link&lt;/strong&gt;: Abre o app diretamente na tela correta.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para termos uma visão completa, o diagrama abaixo ilustra o roteamento (conhecido como &lt;em&gt;Deferred Deep Linking&lt;/em&gt;), 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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiviwp8xhrpjpeu5t7jxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiviwp8xhrpjpeu5t7jxv.png" alt="Fluxo completo" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fluxo completo&lt;/p&gt;

&lt;p&gt;Esse fluxo completo, onde o usuário pode instalar o app e ainda assim manter o contexto original do link, é o que chamamos de &lt;strong&gt;Deferred Deep Linking&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomia de um Deep Link
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://fitconnect.app/signup?referralCode=TRAINER12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheme&lt;/strong&gt;: &lt;code&gt;https&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host&lt;/strong&gt;: &lt;code&gt;fitconnect.app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path&lt;/strong&gt;: &lt;code&gt;/signup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query&lt;/strong&gt;: &lt;code&gt;?referralCode=TRAINER12345&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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).&lt;/p&gt;




&lt;h2&gt;
  
  
  Tipos de Deep Links
&lt;/h2&gt;

&lt;p&gt;Existem dois tipos principais e a escolha entre eles tem implicações diretas na segurança e na experiência do usuário.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Schemes (&lt;code&gt;fitconnect://&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fitconnect://fitconnect.app/signup?referralCode=TRAINER12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Rápido de implementar.&lt;/li&gt;
&lt;li&gt;✅ Ótimo para testes locais.&lt;/li&gt;
&lt;li&gt;⚠️ Menos seguro — qualquer aplicativo pode registrar o mesmo scheme.&lt;/li&gt;
&lt;li&gt;⚠️ Se o app não estiver instalado, o sistema exibe um erro feio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  App Links (Android) / Universal Links (iOS)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://fitconnect.app/signup?referralCode=TRAINER12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Seguro — verificação bidirecional entre aplicativo e servidor.&lt;/li&gt;
&lt;li&gt;✅ Se o app não estiver instalado, abre normalmente no navegador.&lt;/li&gt;
&lt;li&gt;✅ Recomendado para produção.&lt;/li&gt;
&lt;li&gt;⚠️ Requer domínio próprio.&lt;/li&gt;
&lt;li&gt;⚠️ Setup mais complexo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;O que é essa verificação bidirecional?&lt;/strong&gt; 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 (&lt;code&gt;assetlinks.json&lt;/code&gt; no Android, &lt;code&gt;apple-app-site-association&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Na prática: use custom schemes durante o desenvolvimento e migre para HTTPS (App Links / Universal Links) em produção.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nossa Aplicação: FitConnect
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O cenário:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O deep link principal da série:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://fitconnect.app/signup?referralCode=TRAINER12345678901234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando um aluno clica nesse link, o app deve abrir diretamente na tela de cadastro com o código &lt;code&gt;TRAINER12345678901234&lt;/code&gt; já preenchido. Simples de descrever, mas com bastante detalhe de implementação por baixo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domínio&lt;/strong&gt;: &lt;code&gt;fitconnect.app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom scheme&lt;/strong&gt;: &lt;code&gt;fitconnect://&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package&lt;/strong&gt;: &lt;code&gt;com.fitconnect.app&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Preparando a Estrutura
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constantes
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/shared/const/deep_link_const.dart&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeepLinkConst&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;methodChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'com.fitconnect.app/deeplink'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'com.fitconnect.app/deeplink_stream'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'fitconnect'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;httpsScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;appHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'fitconnect.app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;signupPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'/signup'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;referralCodeParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'referralCode'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;referralCodeLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enum de Tipos
&lt;/h3&gt;

&lt;p&gt;O getter &lt;code&gt;isSecure&lt;/code&gt; torna o código mais expressivo — em vez de comparar strings ou verificar o scheme manualmente, você simplesmente checa &lt;code&gt;data.type.isSecure&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/shared/enums/deep_link_type.dart&lt;/span&gt;
&lt;span class="kt"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;customScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;appLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Android HTTPS&lt;/span&gt;
  &lt;span class="n"&gt;universalLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// iOS HTTPS&lt;/span&gt;
  &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;isSecure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;appLink&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;universalLink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Modelo de Dados (Freezed)
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/shared/models/deep_link_data.dart&lt;/span&gt;
&lt;span class="nd"&gt;@freezed&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeepLinkData&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;_$DeepLinkData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;DeepLinkData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;DeepLinkType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Default&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;receivedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_DeepLinkData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  O que construímos até aqui
&lt;/h2&gt;

&lt;p&gt;Ao final desta etapa, você já tem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clareza sobre como deep links funcionam.&lt;/li&gt;
&lt;li&gt;Entendimento das diferenças entre os tipos de links.&lt;/li&gt;
&lt;li&gt;Uma base sólida no Flutter para começar a implementação.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa base será utilizada nos próximos passos para integrar o código nativo e completar o fluxo ponta a ponta.&lt;/p&gt;

&lt;p&gt;Na próxima etapa, vamos partir para a implementação nativa no Android — incluindo &lt;code&gt;AndroidManifest.xml&lt;/code&gt; e &lt;code&gt;MainActivity.kt&lt;/code&gt; completos.&lt;/p&gt;




&lt;p&gt;Código completo disponível no repositório: &lt;a href="https://github.com/crdornelles/fit_connect" rel="noopener noreferrer"&gt;FitConnect no GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;Se esse conteúdo te ajudou, deixa um ❤️ ou um 🔖 aqui no DEV.to — isso ajuda o post a alcançar mais devs.&lt;/p&gt;

&lt;p&gt;E você, já implementou deep links no seu app?&lt;br&gt;&lt;br&gt;
Qual foi o maior desafio que encontrou?&lt;/p&gt;

&lt;p&gt;Quero usar esses casos reais nos próximos posts da série 👇&lt;/p&gt;




&lt;h1&gt;
  
  
  flutter #android #ios #deeplinks #mobile #programming
&lt;/h1&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>deeplinks</category>
    </item>
  </channel>
</rss>
