1. Antes de começar
Resumo rápido sobre links diretos, da web e Links do app Android
O principal objetivo de um usuário ao seguir um link direto é acessar o conteúdo que ele quer conferir. Os links diretos têm todas as funcionalidades para que os usuários consigam fazer isso. O Android processa estes tipos de links:
- Links diretos: são URIs de qualquer esquema que levam os usuários diretamente a uma parte específica do seu app.
- Links da web: são links diretos com o esquema HTTP e HTTPS.
- Links do app Android: são links da web com esquemas HTTP e HTTPS que contêm o atributo
android:autoVerify
.
Para informações mais detalhadas sobre links diretos, da web e Links do app Android, consulte a documentação do Android e o curso intensivo no YouTube (vídeo em inglês) e o artigo no Medium (link em inglês).
Já conhece os Links do app Android?
Se você já conhece todos os detalhes técnicos, confira uma implementação rápida na postagem de blog (link em inglês) complementar para conseguir configurar o recurso em apenas algumas etapas.
Objetivo do codelab
Este codelab mostra as práticas recomendadas para a configuração, implementação e verificação de um app com os Links do app Android.
Um dos benefícios dos Links do app Android é que eles são seguros, o que significa que nenhum app não autorizado pode processar seus links. O SO Android precisa verificar os links com um site de sua propriedade para qualificá-los como Links do app Android. Esse processo é chamado associação de sites.
Este codelab tem como foco os desenvolvedores que têm um site e um app Android. Os Links do app Android fornecem uma experiência melhor do usuário, permitindo uma integração total entre seu app e seu site.
Pré-requisitos
- Conhecimento básico sobre o gerenciador de atividades do adb e o gerenciador de pacotes do adb.
- Conhecimento básico sobre desenvolvimento em Android e navegação com o Jetpack Compose.
O que você vai aprender
- Práticas recomendadas sobre a criação de URLs para Links do app Android.
- Como configurar todos os tipos de links diretos em um app Android.
- Sobre caracteres curinga de caminhos (
path
,pathPrefix
,pathPattern
,pathAdvancePattern
). - Sobre o processo de verificação dos Links do app Android, que inclui o upload do arquivo do Digital Asset Links (DAL) do Google, o processo de verificação manual dos Links do app Android e o painel de links diretos do Google Play Console.
- Como criar um app Android com vários restaurantes em diferentes locais.
O que é necessário
- Android Studio Dolphin (2021.3.1) ou uma versão mais recente.
- Um domínio para hospedar arquivos do Google Digital Asset Links (DAL). Opcional: confira esta postagem de blog (em inglês) que ajuda a configurar esse domínio rapidamente.
- Opcional: uma conta de desenvolvedor do Google Play Console, o que permite uma outra abordagem para depurar a configuração dos Links do app Android.
2. Configurar o código
Criar um aplicativo vazio no Compose
Para começar um projeto no Compose, siga estas etapas:
- No Android Studio, selecione File > New > New Project.
- Selecione a opção Empty Compose Activity nos modelos disponíveis.
- Clique em Next e configure seu projeto com o nome Deep Links Basics. Para o SDK mínimo escolha pelo menos o nível 21 da API, que é o mínimo com suporte do Compose.
- Clique em Finish e aguarde até que seu projeto seja gerado.
- Inicie o aplicativo. Verifique se o app é executado. Uma tela em branco com a mensagem Hello Android! deve aparecer.
Solução para o codelab
O código da solução deste codelab está disponível no GitHub:
git clone https://github.com/android/deep-links
Se preferir, faça o download do repositório como um arquivo ZIP:
Primeiro, acesse o diretório deep-links-introduction. O app vai estar no diretório solution. Recomendamos que você siga todas as etapas do codelab no seu próprio ritmo e consulte a solução só caso precise de ajuda. Durante o codelab, vamos ensinar os snippets de código que precisam ser adicionados ao projeto.
3. Analisar o design de URLs orientados por links diretos
Design da API RESTful
Os links são uma parte essencial do desenvolvimento da Web, e a criação de links tem passado por inúmeras iterações, resultando em vários padrões. Vale a pena conferir e aplicar os padrões de criação de links para desenvolvimento da Web, o que os torna mais fáceis de usar e manter.
Um desses padrões é o REST, que significa Representational State Transfer (Transferência de Estado Representacional, em inglês), uma arquitetura que geralmente é usada para criar APIs para serviços da Web. A OpenAPI (link em inglês) é uma iniciativa que padroniza APIs REST. Você também pode usar a REST para criar URLs de links diretos.
Não estamos criando um serviço da Web. Esta seção foca apenas na criação de URLs.
Como criar os URLs
Primeiro, analise os URLs resultantes no seu site e entenda o que eles representam no app Android:
/restaurants
lista todos os restaurantes que você gerencia./restaurants/:restaurantName
mostra todos os detalhes de um restaurante específico./restaurants/:restaurantName/orders
mostra os pedidos de um restaurante./restaurants/:restaurantName/orders/:orderNumber
mostra um pedido específico de um restaurante./restaurants/:restaurantName/orders/latest
mostra o pedido mais recente de um restaurante.
Por que a criação de um URL é importante
O Android possui filtros de intent que processam ações de outro componente do app e também são usados para capturar URLs. Quando você define um filtro de intent para capturar um URL, precisa seguir uma estrutura que conte com prefixos de caminhos e caracteres curinga claros. Confira um exemplo de como isso é estruturado usando um URL já existente no site de um restaurante:
https://example.com/pawtato-3140-Skinner-Hollow-Road
Embora esse URL especifique seu restaurante e a localização dele, o caminho pode criar um problema ao definir um filtro de intent para o Android capturar o URL, porque seu app tem como base diversos URLs de restaurantes como estes:
https://example.com/rawrbucha-2064-carriage-lane
https://example.com/pizzabus-1447-davis-avenue
Ao definir um filtro de intent com um caminho e um caractere curinga para capturar esses URLs, você pode usar algo como https://example.com/*
, que funciona no geral. No entanto, você não resolve de fato o problema, porque há outros caminhos para diferentes seções do seu site, como:
Entrega: https://example.com/deliveries
Administrador: https://example.com/admin
Você pode não querer que o Android capture esses URLs, porque alguns deles podem ser internos, mas o filtro de intent definido, https://example.com/*
, captura esses e outros caminhos, incluindo URLs inexistentes. E, quando um usuário clica em um deles, o URL é aberto no navegador (com o Android 12 ou mais recente), ou uma caixa de diálogo de desambiguação pode aparecer (com uma versão anterior ao Android 12). Para nosso design, esse não é um comportamento esperado.
Agora o Android oferece prefixos de planos que resolvem esse problema, sem precisar de um novo design de URL. De:
https://example.com/*
Para:
https://example.com/restaurants/*
A adição de uma estrutura de transição hierárquica define claramente seus filtros de intent, e o Android captura o URL que você pedir.
Práticas recomendadas para a criação de URLs
Confira algumas práticas recomendadas pela OpenAPI e aplicadas sob uma perspectiva para links diretos:
- Concentre o foco do design do URL nas entidades comerciais que ele expõe. Por exemplo, em um e-commerce, pode haver clientes e pedidos. Para o setor de viagens, pode ser passagens e voos. No app e site do seu restaurante, você usará restaurantes e pedidos.
- A maioria dos nomes dos métodos HTTP (GET, POST, DELETE, PUT) usa verbos que descrevem a solicitação que está sendo feita, mas pode ser confuso usar verbos para endpoints em URLs.
- Para descrever coleções, use o plural da entidade, por exemplo,
/restaurants/:restaurantName
. Isso deixa o URL mais fácil de ler e manter. Confira um exemplo com cada um dos métodos HTTP:
GET /restaurants/pawtato
POST /restaurants
DELETE /restaurants
PUT /restaurants/pawtato
Fica mais fácil ler cada URL e entender o que ele faz. Este codelab não aborda o design da API de serviços da Web e o que cada método faz.
- Use a transição lógica para agrupar URLs que contêm informações relacionadas. Por exemplo, o URL de um dos restaurantes pode ter os pedidos que estão sendo preparados:
/restaurants/1/orders
4. Analisar elementos de dados
O arquivo AndroidManifest.xml
é uma parte essencial do Android. Ele descreve as informações do app para as ferramentas de build do Android, para o SO Android e para o Google Play.
No caso de links diretos, você precisa definir um filtro de intent usando três tags principais: <action>
, <category>
e <data>
. Nosso foco principal nesta seção é a tag <data>
.
Um elemento <data>
informa ao SO Android a estrutura do URL de um link quando um usuário clicar no link. O formato e a estrutura do URL que você pode usar nos filtros de intents são os seguintes:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>|<pathAdvancedPattern>|<pathSuffix>]
O Android lê, analisa e reúne todos os elementos <data>
de um filtro de intent a serem considerados para todas as variações dos atributos. Por exemplo:
AndroidManifest.xml
<intent-filter>
...
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="example.com" />
<data android:path="/restaurants" />
<data android:pathPrefix="/restaurants/orders" />
</intent-filter>
O Android captura os seguintes URLs:
http://example.com/restaurants
https://example.com/restaurants
http://example.com/restaurants/orders/*
https://example.com/restaurants/orders/*
Atributos de caminho
path
(disponível na API 1)
Este atributo especifica um caminho completo, começando com /
, que corresponde ao caminho completo na intent. Por exemplo, android:path="/restaurants/pawtato"
só corresponde ao caminho do site /restaurants/pawtato
, e se tivermos /restaurant/pawtato
, esse URL não será associado, por causa do s
que está faltando.
pathPrefix
(disponível na API 1)
Esse atributo especifica um caminho parcial que corresponde apenas à parte inicial do caminho da intent. Por exemplo:
android:pathPrefix="/restaurants"
vai corresponder aos caminhos de restaurantes /restaurants/pawtato
, /restaurants/pizzabus
e assim por diante.
pathSuffix
(disponível na API 31)
Esse atributo especifica um caminho que corresponde exatamente à parte final do caminho da intent. Por exemplo:
android:pathSuffix="tato"
corresponderá a todos os caminhos de restaurantes que terminam como tato, por exemplo, /restaurants/pawtato
e /restaurants/corgtato
.
pathPattern
(disponível na API 1)
Esse atributo especifica um caminho completo que corresponde ao caminho completo com caracteres curinga na intent.
- Um asterisco (
*
) corresponde a uma sequência de zero a várias ocorrências do caractere anterior - Um ponto seguido por um asterisco (
.*
) corresponde a qualquer sequência de zero a vários caracteres.
Exemplos:
/restaurants/piz*abus
: este padrão faz a correspondência ao restaurante "pizzabus", mas também corresponde a restaurantes que tenham zero a vários caracteresz
no nome, por exemplo,/restaurants/pizzabus
,/restaurants/pizzzabus
e/restaurants/pizabus
./restaurants/.*
: este padrão corresponde a qualquer nome de restaurante que tenha o caminho/restaurants
, por exemplo,/restaurants/pizzabus
e/restaurants/pawtato
, bem como aqueles que o app não conhece, como/restaurants/wateriehall
.
pathAdvancePattern
(disponível na API 31)
Esse atributo especifica um caminho completo que corresponde ao caminho completo com padrões do estilo regex:
- Um ponto (
.
) faz correspondência com qualquer caractere. - Um conjunto de colchetes (
[...]
) faz a correspondência de uma faixa de caracteres e também oferece suporte ao modificador "not" (^
). - Um asterisco (
*
) faz a correspondência do padrão anterior zero ou mais vezes. - Um sinal de mais (
+
) faz a correspondência do padrão anterior uma ou mais vezes. - As chaves (
{...}
) representam quantas correspondências um padrão pode ter.
Esse atributo pode ser considerado uma extensão de pathPattern
. Ele oferece mais flexibilidade em relação a quais URLs devem ser associados, por exemplo:
/restaurants/[a-zA-Z]*/orders/[0-9]{3}
faz a correspondência de qualquer pedido de restaurante que tenha até três dígitos de comprimento./restaurants/[a-zA-Z]*/orders/latest
faz a correspondência do pedido mais recente de qualquer um dos restaurantes do app
5. Criar links diretos e da web
Links diretos com esquemas personalizados
Links diretos com esquemas personalizados são os tipos mais genéricos de links diretos e são os mais fáceis de implementar, mas há alguns problemas. Esses links não podem ser abertos por sites da Web. Qualquer app que declare oferecer suporte a esse esquema no manifesto pode abrir o link.
É possível usar qualquer esquema no elemento <data>
. Por exemplo, este codelab usa o URL food://restaurants/keybabs
.
- No Android Studio, adicione o filtro de intent abaixo ao arquivo de manifesto:
AndroidManifest.xml
<activity ... >
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="food"/>
<data android:path="/restaurants/keybabs"/>
</intent-filter>
</activity>
- Para conferir se o app pode abrir links com esquemas personalizados, mostre um link na página inicial, adicionando o seguinte à atividade principal:
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Receive the intent action and data
val action: String? = intent?.action;
val data: Uri? = intent?.data;
setContent {
DeepLinksBasicsTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
// Add a Column to print a message per line
Column {
// Print it on the home screen
Greeting("Android")
Text(text = "Action: $action")
Text(text = "Data: $data")
}
}
}
}
}
}
- Para testar se a intent é recebida, use o Android Debug Bridge (adb) com este comando:
adb shell am start -W -a android.intent.action.VIEW -d "food://restaurants/keybabs"
Esse comando inicia uma intent com a ação "VIEW" (visualizar) e usa o URL fornecido como dados. Quando você executa este comando, o app é iniciado e recebe a intent. Observe as mudanças feitas nas seções de texto da tela principal. Uma delas mostra a mensagem Hello Android!, a segunda, mostra a ação que a intent chamou, e a terceira o URL que a intent chamou.
Na imagem abaixo, observe que na parte de baixo da seção do Android Studio, o comando adb
mencionado foi executado. À direita, o app mostra as informações da intent na tela inicial, o que significa que elas foram recebidas.
Links da web
Links da web são links diretos que usam http
e https
, em vez de esquemas personalizados.
Para implementar links da web, use o caminho /restaurants/keybabs/order/latest.html
, que representa o pedido mais recente recebido no restaurante.
- Ajuste o arquivo de manifesto usando o filtro de intent já criado.
AndroidManifest.xml
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="food"/>
<data android:path="/restaurants/keybabs"/>
<!-- Web link configuration -->
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="sabs-deeplinks-test.web.app"/>
<data android:path="/restaurants/keybabs/order/latest.html"/>
</intent-filter>
Como ambos os caminhos estão sendo compartilhados (/restaurants/keybabs
), uma prática recomendada é colocá-los no mesmo filtro de intent, porque isso facilita a implementação e a leitura do arquivo de manifesto.
- Antes de testar o link da web, reinicie o app para que as novas mudanças sejam aplicadas.
- Use o mesmo comando adb para iniciar a intent. Porém, vamos atualizar o URL nesse caso.
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/keybabs/orders/latest.html"
Na captura de tela, observe que a intent é recebida e que o navegador da Web é aberto para mostrar o site, um recurso do Android 12 e versões mais recentes.
6. Configurar Links do app Android
Esses links oferecem a melhor experiência do usuário, porque quando um usuário clica em um link, ele com certeza é direcionado ao app sem caixas de diálogo de desambiguação. O recurso de Links do app Android foi implementado no Android 6.0 e é o tipo mais específico de link direto. Alguns links da web usam o esquema http/https
e o atributo android:autoVerify
, que torna o app o gerenciador padrão de qualquer link correspondente. Há duas etapas principais para implementar os Links do app Android:
- Atualizar o arquivo de manifesto com o filtro de intent adequado.
- Adicionar a associação de sites para verificação.
Atualizar o arquivo de manifesto
- Para oferecer suporte aos Links do app Android, no arquivo de manifesto, substitua a configuração antiga por esta:
AndroidManifest.xml
<!-- Replace deep link and web link configuration with this -->
<!-- Please update the host with your own domain -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https"/>
<data android:host="example.com"/>
<data android:pathPrefix="/restaurants"/>
</intent-filter>
Este filtro de intent adiciona o atributo android:autoVerify
e o define como "true". Dessa forma, o SO Android pode verificar o domínio quando o aplicativo está instalado e a cada nova atualização.
Associação de sites
Para validar um Link do app Android, crie uma associação entre o aplicativo e o site. Um arquivo JSON Google Digital Asset Links (DAL) precisa ser publicado no site para que a validação ocorra.
Google DAL é um protocolo e uma API que define instruções verificáveis sobre outros apps e sites. Neste codelab, você criará uma instrução sobre o app Android no arquivo assetlinks.json
. Confira um exemplo:
assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.devrel.deeplinksbasics",
"sha256_cert_fingerprints":
["B0:4E:29:05:4E:AB:44:C6:9A:CB:D5:89:A3:A8:1C:FF:09:6B:45:00:C5:FD:D1:3E:3E:12:C5:F3:FB:BD:BA:D3"]
}
}]
Este arquivo pode armazenar uma lista de instruções, mas o exemplo mostra apenas um item. Cada instrução precisa conter os campos abaixo:
- Relation: descreve uma ou mais relações que estão sendo declaradas sobre o valor desejado.
- Target. O recurso a que a instrução se aplica. Pode ser um de dois valores disponíveis:
web
ouandroid_app
.
A propriedade target
da instrução do Android contém estes campos:
namespace
: oandroid_app
de todos os apps Android.package_name
: o nome totalmente qualificado do pacote (com.devrel.deeplinksbasics
).sha256_cert_fingerprints
: a impressão digital do certificado do app. Você aprenderá como gerar esse certificado na próxima seção.
Impressão digital do certificado
Há vários métodos para extrair a impressão digital do certificado. Este codelab usa dois métodos: um para o build de depuração do aplicativo e outro para ajudar a lançar o app na Google Play Store.
Configuração de depuração
Na primeira vez que o Android Studio executa seu projeto, ele assina automaticamente o app com um certificado de depuração. Este certificado fica em $HOME/.android/debug.keystore
. Você pode usar um comando do Gradle para extrair essa impressão digital de certificado SHA-256, seguindo estas etapas:
- Pressione
Control
duas vezes. O menu Run anything vai aparecer. Se ele não aparecer, você poderá encontrá-lo no menu do Gradle na barra lateral à direita e, em seguida, clicar no ícone do Gradle.
- Digite
gradle signingReport
e pressioneEnter
. O comando é executado no console e mostra as informações de impressão digital da variante do app de depuração.
- Para concluir a associação do site, copie a impressão digital do certificado SHA-256, atualize o arquivo JSON e faça o upload no seu site, em
https://<domain>/.well-know/assetlinks.json
. Esta postagem do blog sobre Links do app Android (em inglês) ajudará você com a configuração. - Se o app ainda estiver em execução, pressione Stop para interrompê-lo.
- Para iniciar o processo de verificação de novo, remova o app do simulador. No simulador, clique e mantenha pressionado o ícone do app DeepLinksBasics e selecione App Info. Clique em Uninstall e em Confirm no modal. Em seguida, execute o aplicativo para que o Android Studio possa verificar a associação.
- Selecione a configuração de execução app. Caso contrário, o relatório de assinaturas do Gradle será executado novamente.
- Reinicie o app e a intent com o URL do Link do app Android:
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/"
- Observe que o app é iniciado, e a intent aparece na tela inicial.
Parabéns, você acabou de criar seu primeiro Link do app Android!
Configuração de lançamento
Agora, para fazer o upload do seu aplicativo com os Links do app Android na Play Store, você precisa usar um build de lançamento com a impressão digital do certificado adequada. Para gerar e fazer o upload do build, siga estas etapas:
- No menu principal do Android Studio, clique em Build > Generate Signed Bundle/APK.
- Na caixa de diálogo que aparecer em seguida, selecione Android App Bundle para usar a Assinatura de apps do Google, ou APK, se você estiver fazendo a implantação diretamente em um dispositivo.
- Na próxima caixa de diálogo, em Key store path, clique em Create new. Uma nova janela vai aparecer.
- Selecione um caminho para seu keystore e dê a ele o nome de
basics-keystore.jks
. - Crie e confirme uma senha para o keystore.
- Deixe o padrão para a chave Alias.
- Verifique se a senha e a confirmação são as mesmas no keystore. Elas precisam ser iguais.
- Preencha as informações em Certificate e clique em OK.
- A opção para exportar as chaves criptografadas precisa estar selecionada para a Assinatura de apps do Google Play. Clique em Next.
- Nesta caixa de diálogo, selecione a variante do build de lançamento e clique em Finish. Agora você pode fazer o upload do seu app na Google Play Store e usar o recurso de Assinatura de apps do Google Play.
Assinatura de apps do Google Play
Com o recurso de Assinatura de apps do Google Play, o Google ajuda você a gerenciar e proteger as chaves de assinaturas do seu app. Você só precisa fazer o upload do pacote assinado do app, que você aprendeu na etapa anterior.
Para extrair a impressão digital do certificado do arquivo assetlinks.json
e usar seus Links do app Android na variante do build de lançamento, siga estas etapas:
- No Google Play Console, clique em Criar app.
- Para o nome do app, digite Deep Links Basics.
- Selecione App e Gratuito para as duas próximas opções.
- Aceite as Declarações e clique em Criar app.
- Para fazer o upload do pacote e poder testar os Links do app Android, selecione Testes > Teste interno.
- Clique em Criar nova versão.
- Na próxima tela, clique em Upload e selecione o pacote gerado na seção anterior. Acesse o arquivo
app-release.aab
em DeepLinksBasics > app > release. Clique em Abrir e espere até que o pacote seja enviado. - Depois de enviado, deixe os arquivos restantes com os respectivos padrões por enquanto. Clique em Salvar.
- Para se preparar para a próxima seção, clique em Avaliar versão e, em seguida, na tela seguinte, clique em Começar lançamento para testes internos. Ignore os avisos porque a atividade de publicação na Play Store está fora do escopo deste codelab.
- Clique em Lançar no modal.
- Para extrair a impressão digital do certificado SHA-256 que a Assinatura de apps do Google Play criou, acesse a guia Links diretos, no menu à esquerda e, em seguida, confira o painel de links diretos.
- Na seção Domínios, clique no domínio do site. O Google Play Console informará que você não validou o domínio com seu app (associação de sites).
- Na seção Corrigir problemas de domínio, clique na seta Mostrar mais.
- Nessa tela, o Google Play Console mostra como atualizar o arquivo
assetlinks.json
com a impressão digital do certificado. Copie o snippet de código e atualize o arquivoassetlinks.json
.
- Após atualizar o arquivo
assetlinks.json
, clique em Repetir verificação. Caso a verificação ainda não tenha sido aprovada, aguarde cinco minutos para que o serviço de verificação possa detectar as novas mudanças. - Se você recarregar a página do painel Links diretos, vai notar que não há mais erros de verificação.
Verificação de um app enviado
Você já sabe como verificar um app no simulador. Agora, você vai verificar seu app que foi enviado à Play Store.
Para instalar o aplicativo no emulador e garantir que os Links do app Android sejam aprovados, siga estas etapas:
- No painel esquerdo, clique em Visão geral das versões e selecione a versão mais recente que você acabou de enviar. Deve ser a versão 1 (1.0).
- Clique em Detalhes da versão (seta azul à direita) para conferir os detalhes.
- Clique no mesmo botão da seta azul à direita para acessar as informações sobre o pacote do app.
- Neste modal, selecione a guia Downloads e, em seguida, clique em download para o recurso APK universal e assinado.
- Antes de instalar este pacote no simulador, exclua o aplicativo anterior instalado pelo Android Studio.
- No simulador, clique e mantenha pressionado o ícone do app DeepLinksBasics e selecione App Info. Clique em Uninstall e em Confirm no modal.
- Para instalar o pacote, arraste o arquivo
1.apk
transferido por download e solte na tela do simulador e espere até que ele seja instalado.
- Para testar a validação, abra o terminal no Android Studio e execute o processo de verificação usando estes dois comandos:
adb shell pm verify-app-links --re-verify com.devrel.deeplinksbasics
adb shell pm get-app-links com.devrel.deeplinksbasics
- Depois do comando
get-app-links
, uma mensagemverified
vai aparecer no console. Se você receber uma mensagemlegacy_failure
, verifique se a impressão digital do certificado é igual à que você enviou para o site. Se forem iguais e a mensagem de verificação ainda não aparecer, tente executar as etapas 6, 7 e 8 de novo.
7. Implementar Links do app Android
Agora que tudo está configurado, é hora de implementar o app.
O Jetpack Compose será usado para a implementação. Para saber mais sobre o Jetpack Compose, consulte Crie apps melhores com mais rapidez usando o Jetpack Compose.
Dependência de códigos
Para incluir e atualizar algumas dependências que você precisa para este projeto:
- Adicione o seguinte aos arquivos
Module
eProject
do Gradle:
build.gradle Projeto (link em inglês)
buildscript {
...
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:2.43"
}
}
build.gradle Módulo (link em inglês)
plugins {
...
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
...
dependencies {
...
implementation 'androidx.compose.material:material:1.2.1'
...
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
implementation "com.google.dagger:hilt-android:2.43"
kapt "com.google.dagger:hilt-compiler:2.43"
}
O arquivo zip do projeto contém um diretório com 10 imagens livres de royalties que podem ser usadas para cada restaurante. Você pode usá-las ou incluir imagens próprias.
Para adicionar o ponto de entrada principal para o HiltAndroidApp
, siga esta etapa:
- Crie um novo arquivo de classe do Kotlin com o nome
DeepLinksBasicsApplication.kt
e atualize o arquivo de manifesto com o nome do novo app.
DeepLinksBasicsApplication.kt (link em inglês)
package com.devrel.deeplinksbasics
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class DeepLinksBasicsApplication : Application() {}
AndroidManifest.xml (link em inglês)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Update name property -->
<application
android:name=".DeepLinksBasicsApplication"
...
Dados
Você precisa criar uma camada de dados para os restaurantes com uma classe Restaurant
, um repositório e uma origem de dados locais. Todos os componentes precisam ficar em um pacote data
que você terá que criar. Para isso, siga estas etapas:
- No arquivo
Restaurant.kt
, crie uma classeRestaurant
com este snippet de código:
Restaurant.kt (link em inglês)
package com.devrel.deeplinksbasics.data
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
@Immutable
data class Restaurant(
val id: Int = -1,
val name: String = "",
val address: String = "",
val type: String = "",
val website: String = "",
@DrawableRes val drawable: Int = -1
)
- No arquivo
RestaurantLocalDataSource.kt
, adicione alguns restaurantes na classe de origem de dados. Não se esqueça de atualizar os dados com seu domínio. Confira este snippet de código:
RestaurantLocalDataSource.kt (link em inglês)
package com.devrel.deeplinksbasics.data
import com.devrel.deeplinksbasics.R
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RestaurantLocalDataSource @Inject constructor() {
val restaurantList = listOf(
Restaurant(
id = 1,
name = "Pawtato",
address = "3140 Skinner Hollow Road, Medford, Oregon 97501",
type = "Potato and gnochi",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/pawtato/",
drawable = R.drawable.restaurant1,
),
Restaurant(
id = 2,
name = "Rawrbucha",
address = "2064 Carriage Lane, Mansfield, Ohio 44907",
type = "Kombucha",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/rawrbucha/",
drawable = R.drawable.restaurant2,
),
Restaurant(
id = 3,
name = "Pizzabus",
address = "1447 Davis Avenue, Petaluma, California 94952",
type = "Pizza",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/pizzabus/",
drawable = R.drawable.restaurant3,
),
Restaurant(
id = 4,
name = "Keybabs",
address = "3708 Pinnickinnick Street, Perth Amboy, New Jersey 08861",
type = "Kebabs",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/keybabs/",
drawable = R.drawable.restaurant4,
),
Restaurant(
id = 5,
name = "BBQ",
address = "998 Newton Street, Saint Cloud, Minnesota 56301",
type = "BBQ",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/bbq/",
drawable = R.drawable.restaurant5,
),
Restaurant(
id = 6,
name = "Salades",
address = "4522 Rockford Mountain Lane, Oshkosh, Wisconsin 54901",
type = "salads",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/salades/",
drawable = R.drawable.restaurant6,
),
Restaurant(
id = 7,
name = "Gyros and moar",
address = "1993 Bird Spring Lane, Houston, Texas 77077",
type = "Gyro",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/gyrosAndMoar/",
drawable = R.drawable.restaurant7,
),
Restaurant(
id = 8,
name = "Peruvian ceviche",
address = "2125 Deer Ridge Drive, Newark, New Jersey 07102",
type = "seafood",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/peruvianCeviche/",
drawable = R.drawable.restaurant8,
),
Restaurant(
id = 9,
name = "Vegan burgers",
address = "594 Warner Street, Casper, Wyoming 82601",
type = "vegan",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/veganBurgers/",
drawable = R.drawable.restaurant9,
),
Restaurant(
id = 10,
name = "Taquitos",
address = "1654 Hart Country Lane, Blue Ridge, Georgia 30513",
type = "mexican",
// TODO: Update with your own domain
website = "https://your.own.domain/restaurants/taquitos/",
drawable = R.drawable.restaurant10,
),
)
}
- Não se esqueça de importar imagens para seu projeto.
- Em seguida, no arquivo
RestaurantRepository.kt
, adicione o repositórioRestaurant
com uma função para acessar um restaurante pelo nome, como neste snippet de código:
RestaurantRepository.kt (link em inglês)
package com.devrel.deeplinksbasics.data
import javax.inject.Inject
class RestaurantRepository @Inject constructor(
private val restaurantLocalDataSource: RestaurantLocalDataSource
){
val restaurants: List<Restaurant> = restaurantLocalDataSource.restaurantList
// Method to obtain a restaurant object by its name
fun getRestaurantByName(name: String): Restaurant ? {
return restaurantLocalDataSource.restaurantList.find {
val processedName = it.name.filterNot { it.isWhitespace() }.lowercase()
val nameToTest = name.filterNot { it.isWhitespace() }.lowercase()
nameToTest == processedName
}
}
}
ViewModel
Para selecionar um restaurante pelo app e com um Link do app Android, você precisa criar um ViewModel
que mude o valor do restaurante selecionado. Faça o seguinte:
- No arquivo
RestaurantViewModel.kt
, adicione este snippet de código:
RestaurantViewModel.kt (link em inglês)
package com.devrel.deeplinksbasics.ui.restaurant
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.devrel.deeplinksbasics.data.Restaurant
import com.devrel.deeplinksbasics.data.RestaurantRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class RestaurantViewModel @Inject constructor(
private val restaurantRepository: RestaurantRepository,
) : ViewModel() {
// restaurants and selected restaurant could be used as one UIState stream
// which will scale better when exposing more data.
// Since there are only these two, it is okay to expose them as separate streams
val restaurants: List<Restaurant> = restaurantRepository.restaurants
private val _selectedRestaurant = MutableStateFlow<Restaurant?>(value = null)
val selectedRestaurant: StateFlow<Restaurant?>
get() = _selectedRestaurant
// Method to update the current restaurant selection
fun updateSelectedRestaurantByName(name: String) {
viewModelScope.launch {
val selectedRestaurant: Restaurant? = restaurantRepository.getRestaurantByName(name)
if (selectedRestaurant != null) {
_selectedRestaurant.value = selectedRestaurant
}
}
}
}
Compose
Agora que você tem a lógica do modelo de visualização e as camadas de dados, precisa adicionar uma camada de interface. Graças à biblioteca do Jetpack, é possível fazer isso em apenas algumas etapas. Neste app, você quer renderizar os restaurantes em grades modulares. O usuário pode clicar nos cards e conferir detalhes de cada restaurante. Você precisa de três funções combináveis principais e um componente de navegação que leve até o restaurante correspondente.
Para adicionar uma camada de interface, siga estas etapas:
- Comece com a função combinável que renderiza detalhes de cada restaurante. No arquivo
RestaurantCardDetails.kt
, adicione este snippet de código:
RestaurantCardDetails.kt (link em inglês)
package com.devrel.deeplinksbasics.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.devrel.deeplinksbasics.data.Restaurant
@Composable
fun RestaurantCardDetails (
restaurant: Restaurant,
onBack: () -> Unit,
) {
BackHandler() {
onBack()
}
Scaffold(
topBar = {
TopAppBar(
backgroundColor = Color.Transparent,
elevation = 0.dp,
) {
Row(
horizontalArrangement = Arrangement.Start,
modifier = Modifier.padding(start = 8.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Arrow Back",
modifier = Modifier.clickable {
onBack()
}
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = restaurant.name)
}
}
}
) { paddingValues ->
Card(
modifier = Modifier
.padding(paddingValues)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(corner = CornerSize(8.dp))
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = restaurant.name, style = MaterialTheme.typography.h6)
Text(text = restaurant.type, style = MaterialTheme.typography.caption)
Text(text = restaurant.address, style = MaterialTheme.typography.caption)
SelectionContainer {
Text(text = restaurant.website, style = MaterialTheme.typography.caption)
}
Image(painter = painterResource(id = restaurant.drawable), contentDescription = "${restaurant.name}")
}
}
}
}
- Em seguida, implemente a grade e as células dela. No arquivo
RastaurantCell.kt
, adicione este snippet de código:
RestaurantCell.kt (link em inglês)
package com.devrel.deeplinksbasics.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.devrel.deeplinksbasics.data.Restaurant
@Composable
fun RestaurantCell(
restaurant: Restaurant
){
Card(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 8.dp)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(corner = CornerSize(8.dp))
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = restaurant.name, style = MaterialTheme.typography.h6)
Text(text = restaurant.address, style = MaterialTheme.typography.caption)
Image(painter = painterResource(id = restaurant.drawable), contentDescription = "${restaurant.name}")
}
}
}
- No arquivo
RestaurantGrid.kt
, adicione este snippet de código:
RestaurantGrid.kt (link em inglês)
package com.devrel.deeplinksbasics.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.devrel.deeplinksbasics.data.Restaurant
@Composable
fun RestaurantGrid(
restaurants: List<Restaurant>,
onRestaurantSelected: (String) -> Unit,
navigateToRestaurant: (String) -> Unit,
) {
Scaffold(topBar = {
TopAppBar(
backgroundColor = Color.Transparent,
elevation = 0.dp,
) {
Text(text = "Restaurants", fontWeight = FontWeight.Bold)
}
}) { paddingValues ->
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 200.dp),
modifier = Modifier.padding(paddingValues)
) {
items(items = restaurants) { restaurant ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = {
onRestaurantSelected(restaurant.name)
navigateToRestaurant(restaurant.name)
})
) {
RestaurantCell(restaurant)
}
}
}
}
}
- Agora, você precisa implementar o estado do aplicativo e a lógica de navegação e atualizar o arquivo
MainActivity.kt
. Isso pode direcionar para um restaurante específico quando um usuário clicar no card do restaurante. No arquivoRestaurantAppState.kt
, adicione este snippet de código:
RestaurantAppState.kt (link em inglês)
package com.devrel.deeplinksbasics.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
sealed class Screen(val route: String) {
object Grid : Screen("restaurants")
object Name : Screen("restaurants/{name}") {
fun createRoute(name: String) = "restaurants/$name"
}
}
@Composable
fun rememberRestaurantAppState(
navController: NavHostController = rememberNavController(),
) = remember(navController) {
RestaurantAppState(navController)
}
class RestaurantAppState(
val navController: NavHostController,
) {
fun navigateToRestaurant(restaurantName: String) {
navController.navigate(Screen.Name.createRoute(restaurantName))
}
fun navigateBack() {
navController.popBackStack()
}
}
- Para a navegação, você precisa criar o
NavHost
e usar as rotas combináveis para fazer o direcionamento a cada restaurante. No arquivoRestaurantApp.kt
, adicione este snippet de código:
RestaurantApp.kt (link em inglês)
package com.devrel.deeplinksbasics.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.devrel.deeplinksbasics.ui.restaurant.RestaurantViewModel
@Composable
fun RestaurantApp(
viewModel: RestaurantViewModel = viewModel(),
appState: RestaurantAppState = rememberRestaurantAppState(),
) {
val selectedRestaurant by viewModel.selectedRestaurant.collectAsState()
val onRestaurantSelected: (String) -> Unit = { viewModel.updateSelectedRestaurantByName(it) }
NavHost(
navController = appState.navController,
startDestination = Screen.Grid.route,
) {
// Default route that points to the restaurant grid
composable(Screen.Grid.route) {
RestaurantGrid(
restaurants = viewModel.restaurants,
onRestaurantSelected = onRestaurantSelected,
navigateToRestaurant = { restaurantName ->
appState.navigateToRestaurant(restaurantName)
},
)
}
// Route for the navigation to a particular restaurant when a user clicks on it
composable(Screen.Name.route) {
RestaurantCardDetails(restaurant = selectedRestaurant!!, onBack = appState::navigateBack)
}
}
}
- Agora está tudo pronto para você atualizar o arquivo
MainActivity.kt
com a instância do app. Substitua o arquivo por este código:
MainActivity.kt (link em inglês)
package com.devrel.deeplinksbasics
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import com.devrel.deeplinksbasics.ui.RestaurantApp
import com.devrel.deeplinksbasics.ui.theme.DeepLinksBasicsTheme
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DeepLinksBasicsTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
RestaurantApp()
}
}
}
}
}
- Execute o aplicativo para navegar pela grade e selecionar um restaurante específico. Quando você selecionar um restaurante, ele vai aparecer no app com os respectivos dados.
Links do app Android
Agora, adicione os Links do app Android à grade e a cada restaurante. Você já tem a seção AndroidManifest.xml
para a grade em /restaurants
. Você pode usar essa mesma base para cada restaurante, basta adicionar uma nova configuração de rota à sua lógica. Para isso, siga estas etapas:
- Atualize o arquivo de manifesto com o filtro de intent para receber
/restaurants
como um caminho e não se esqueça de incluir seu domínio como um host. No arquivoAndroidManifest.xml
, adicione este snippet de código:
AndroidManifest.xml (link em inglês)
...
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="your.own.domain"/>
<data android:pathPrefix="/restaurants"/>
</intent-filter>
- No arquivo
RestaurantApp.kt
, adicione este snippet de código:
RestaurantApp.kt (link em inglês)
...
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
fun RestaurantApp(...){
NavHost(...){
...
// Route for the navigation to a particular restaurant when a user clicks on it
// and for an incoming deep link
// Update with your own domain
composable(Screen.Name.route,
deepLinks = listOf(
navDeepLink { uriPattern = "https://your.own.domain/restaurants/{name}" }
),
arguments = listOf(
navArgument("name") {
type = NavType.StringType
}
)
) { entry ->
val restaurantName = entry.arguments?.getString("name")
if (restaurantName != null) {
LaunchedEffect(restaurantName) {
viewModel.updateSelectedRestaurantByName(restaurantName)
}
}
selectedRestaurant?.let {
RestaurantCardDetails(
restaurant = it,
onBack = appState::navigateBack
)
}
}
}
}
Internamente, o NavHost
associa os dados do Uri
da intent do Android a rotas combináveis. Se uma rota corresponder à intent, o composable
será renderizado.
O componente composable
pode receber um parâmetro deepLinks
, que contém uma lista dos URIs recebidos do filtro da intent. Neste codelab, você vai adicionar o site criado e definir o parâmetro de identificação a ser recebido e encaminhar o usuário a esse restaurante específico.
- Para garantir que a lógica do app encaminhe o usuário ao restaurante correspondente depois dele clicar em um Link do app Android, use
adb
:
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/gyrosAndMoar"
O app mostrará o restaurante correspondente.
8. Analisar o painel do Google Play Console
Você já analisou o painel de links diretos. Esse painel fornece todas as informações necessárias para garantir que seus links diretos estejam funcionando corretamente Você pode até mesmo analisar por versão do app. Ele mostra o domínio e links comuns e personalizados que foram adicionados ao seu arquivo de manifesto. Também mostra onde atualizar o arquivo assetlinks.json
, caso haja algum problema.
9. Conclusão
Parabéns! Você criou seu primeiro app com os Links do app Android.
Agora você conhece o processo para desenvolver, configurar, criar e testar os Links do app Android. Esse processo possui muitas etapas. Este codelab reúne todos esses detalhes para que você tenha sucesso ao desenvolver para o SO Android.
Agora você conhece as principais etapas necessárias para que os Links do app Android funcionem.
Leia mais (links em inglês)
- Introdução a links diretos
- Links diretos: do básico ao avançado
- Como solucionar problemas com seus links diretos