Deep links e universal links: abrindo telas específicas de fora do app

Em um ecossistema mobile cada vez mais integrado, a capacidade de direcionar usuários diretamente para conteúdo específico dentro de um aplicativo é um diferencial competitivo crucial. Deep links e universal links são tecnologias que permitem exatamente isso: abrir telas específicas do seu app a partir de fontes externas como e-mails, notificações push, campanhas de marketing ou até mesmo outros aplicativos. Neste artigo, exploraremos os fundamentos, configurações, implementações práticas e boas práticas dessas técnicas, sempre com exemplos de código aplicáveis.

Deep links são URIs que direcionam o usuário para um conteúdo específico dentro de um aplicativo, em vez de apenas abrir a tela inicial. Eles funcionam como atalhos que transportam o usuário diretamente para a experiência desejada.

Diferença entre deep links tradicionais e universal/App Links:

  • Deep links tradicionais (URI schemes): Usam esquemas personalizados como meuapp://produto/123. São simples de configurar, mas não possuem verificação de domínio, o que pode gerar conflitos se outro app registrar o mesmo esquema.
  • Universal Links (iOS) e App Links (Android): Usam URLs HTTP/HTTPS padrão (ex: https://meusite.com/produto/123). A Apple e o Google verificam a propriedade do domínio, garantindo maior segurança e evitando conflitos.

Casos de uso comuns:
- Campanhas de marketing que levam a ofertas específicas
- Notificações push que abrem o item notificado
- Compartilhamento de conteúdo entre usuários
- Redefinição de senha com link direto para a tela de redefinição

2. Configuração de URI Schemes Personalizados

iOS (Info.plist)

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>meuapp</string>
        </array>
    </dict>
</array>

Android (AndroidManifest.xml)

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="meuapp" android:host="produto" />
    </intent-filter>
</activity>

Tratamento de parâmetros dinâmicos:

// Exemplo de link: meuapp://produto/123?ref=email
// No iOS (AppDelegate):
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    let path = url.path // "/produto/123"
    let query = url.query // "ref=email"
    // Lógica de navegação
    return true
}

Limitações: Se outro aplicativo registrar o mesmo esquema meuapp, o sistema operacional pode abrir qualquer um deles, criando uma experiência imprevisível para o usuário.

Para implementar Universal Links, você precisa de:
1. Um servidor HTTPS com o arquivo apple-app-site-association
2. Configuração do domínio associado no Xcode

Arquivo apple-app-site-association (servidor):

{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "TEAMID.com.seuapp.bundle",
                "paths": ["/produto/*", "/categoria/*"]
            }
        ]
    }
}

Configuração no Xcode:
- Adicione o domínio em "Associated Domains" com o prefixo applinks:meusite.com

Implementação no AppDelegate:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else { return false }

    // Extrai o path e navega para a tela correspondente
    let path = url.path // "/produto/123"
    handleNavigation(path: path)
    return true
}

No Android, a configuração envolve Digital Asset Links para verificar a propriedade do domínio.

Arquivo assetlinks.json (servidor):

[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
        "namespace": "android_app",
        "package_name": "com.seuapp.package",
        "sha256_cert_fingerprints": ["SEU_FINGERPRINT_AQUI"]
    }
}]

Intent filter no AndroidManifest.xml:

<activity android:name=".MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="meusite.com" android:pathPrefix="/produto" />
    </intent-filter>
</activity>

Implementação com NavigationComponent:

// No arquivo de navegação (NavHost.kt)
composable(
    route = "produto/{id}",
    arguments = listOf(navArgument("id") { type = NavType.StringType })
) { backStackEntry ->
    val id = backStackEntry.arguments?.getString("id")
    ProdutoScreen(id = id)
}

Usando a API Linking nativa do React Native:

import { Linking } from 'react-native';

// Para capturar links quando o app está em primeiro plano
useEffect(() => {
    const handleDeepLink = (event) => {
        const url = event.url;
        // Processa o URL e navega
        navigateToScreen(url);
    };

    Linking.addEventListener('url', handleDeepLink);
    return () => {
        Linking.removeEventListener('url', handleDeepLink);
    };
}, []);

// Para verificar se o app foi aberto por um link
useEffect(() => {
    Linking.getInitialURL().then(url => {
        if (url) {
            navigateToScreen(url);
        }
    });
}, []);

Com react-navigation e linking config:

const linking = {
    prefixes: ['https://meusite.com', 'meuapp://'],
    config: {
        screens: {
            Produto: 'produto/:id',
            Categoria: 'categoria/:nome',
        },
    },
};

// No NavigationContainer
<NavigationContainer linking={linking}>
    {/* Suas telas */}
</NavigationContainer>

Mapeamento de parâmetros:

function handleNavigation(url) {
    const regex = /https:\/\/meusite\.com\/produto\/(\d+)/;
    const match = url.match(regex);
    if (match) {
        const produtoId = match[1];
        navigation.navigate('Produto', { id: produtoId });
    }
}

Tratamento de fallback (app não instalado):

// No servidor web, redirecione para a loja:
if (appNaoInstalado) {
    window.location.href = 'https://apps.apple.com/br/app/seuapp/id123';
}

Lógica de autenticação: Antes de navegar, verifique se o usuário está logado. Caso contrário, redirecione para a tela de login e, após autenticação, complete a navegação original.

iOS (simulador):

xcrun simctl openurl booted "meuapp://produto/123"
xcrun simctl openurl booted "https://meusite.com/produto/123"

Android (emulador ou dispositivo):

adb shell am start -W -a android.intent.action.VIEW -d "meuapp://produto/123" com.seuapp.package
adb shell am start -W -a android.intent.action.VIEW -d "https://meusite.com/produto/123" com.seuapp.package

Validação de assinatura de domínio:
- iOS: Use o site de validação da Apple ou ferramentas como curl -i https://meusite.com/apple-app-site-association
- Android: Use o Digital Asset Links API: https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://meusite.com&relation=delegate_permission/common.handle_all_urls

8. Boas Práticas e Considerações de Segurança

  • Validação de origem: Sempre valide a origem do link para evitar ataques de phishing. Nunca confie cegamente em parâmetros recebidos.
  • HTTPS obrigatório: Universal Links e App Links exigem HTTPS. Nunca use HTTP para esses fins.
  • Cache e fallback: Implemente estratégias para links expirados ou inválidos, como redirecionar para a tela inicial do app ou para uma página de erro amigável.
  • Testes em produção: Teste os links em ambientes de staging e produção para garantir que a configuração do servidor está correta.

Deep links e universal links transformam a experiência do usuário, criando um fluxo contínuo entre o mundo web e o aplicativo. Com as técnicas e boas práticas apresentadas, você pode implementar uma navegação eficiente, segura e intuitiva.

Referências