1. Antes de comenzar
Repaso rápido sobre vínculos directos, vínculos web y Android App Links
El objetivo principal de un usuario cuando hace clic en un vínculo directo es obtener el contenido que desea ver. Todos los vínculos directos tienen la función de ayudar a los usuarios a lograr este objetivo. Android utiliza los siguientes tipos de vínculos:
- Vínculos directos: URIs de cualquier esquema que llevan a los usuarios a una parte específica de tu app
- Vínculos web: Vínculos directos con el esquema HTTP y HTTPS
- Android App Links: Vínculos web y esquemas HTTP y HTTPS que incluyen el atributo
android:autoVerify
Para obtener información más detallada sobre los vínculos directos, los vínculos web y Android App Links, consulta la documentación de Android y el curso rápido en YouTube y Medium.
¿Ya conoces Android App Links?
Si ya conoces todos los detalles técnicos, consulta una implementación rápida en la entrada de blog complementaria que te ayudará con la configuración en unos pocos pasos.
Objetivo del codelab
En este codelab, aprenderás las prácticas recomendadas para el proceso de configuración, implementación y verificación de una app con Android App Links.
Una de las ventajas de Android App Links es que son vínculos seguros, de manera que las apps no autorizadas no podrán controlarlos. Para que califiquen como Android App Links, el SO Android debe verificar los vínculos con un sitio web que te pertenece. Este proceso se llama asociación con el sitio web.
Este codelab está destinado principalmente a los desarrolladores con un sitio web y una app para Android. Android App Links ofrece una mejor experiencia del usuario a través de la integración continua entre tu app y tu sitio web.
Requisitos previos
- Conocimiento básico sobre el administrador de actividades de adb y el administrador de paquetes de adb
- Conocimiento básico sobre la navegación y el desarrollo para Android con Jetpack Compose
Qué aprenderás
- Prácticas recomendadas para el diseño de URLs para Android App Links
- Configurar todos los tipos de vínculos directos en una aplicación para Android
- Comprender los comodines de rutas de acceso (
path
,pathPrefix
,pathPattern
,pathAdvancePattern
) - El proceso de verificación de Android App Links, que incluye la carga del archivo de Vínculos de recursos digitales (DAL) de Google, el proceso de verificación manual de Android App Links y el panel de vínculos directos de Play Developer Console
- Compilar una app para Android con distintos restaurantes en diferentes ubicaciones
Requisitos
- Android Studio Dolphin (2021.3.1) o una versión más reciente
- Un dominio para alojar los archivos de Vínculos de recursos digitales (DAL) de Google (opcional: Lee esta entrada de blog, que te ayudará a tenerlo listo en minutos.)
- Opcional: Cuenta de desarrollador de Google Play Console (de esta manera, tendrás otro enfoque para depurar tu configuración de Android App Links)
2. Código de configuración
Cómo crear una aplicación para Compose vacía
Para comenzar un proyecto de Compose, sigue estos pasos:
- En Android Studio, selecciona File > New > New Project.
- Selecciona Empty Compose Activity entre las plantillas disponibles.
- Haz clic en Next y configura tu proyecto con el nombre Deep Links Basics. Elige una versión mínima del SDK de, el menos, nivel de API 21 (el nivel de API mínimo que admite Compose).
- Haz clic en Finish y espera a que se genere tu proyecto.
- Inicia la aplicación. Asegúrate de que se ejecute la app. Debería haber una pantalla en blanco con el mensaje Hello Android!.
Solución del codelab
Puedes obtener el código de la solución de este codelab en GitHub:
git clone https://github.com/android/deep-links
También tienes la opción de descargar el repositorio como archivo ZIP:
Primero, ve al directorio de deep-links-introduction. Encontrarás la app en el directorio de solution. Te recomendamos seguir el codelab paso a paso, a tu propio ritmo, y consultar la solución si necesitas ayuda. Durante el codelab, recibirás fragmentos de código que deberás agregar al proyecto.
3. Revisa el diseño de URL orientado para vínculos directos
Diseño de la API de RESTful
Los vínculos son una parte fundamental del desarrollo web, y su diseño pasó por innumerables iteraciones, lo que generó distintos estándares. Vale la pena analizar y aplicar los estándares de diseño de vínculos para el desarrollo web, que facilitan su uso y mantenimiento.
Uno de estos estándares es REST (transferencia de estado representacional), una arquitectura que generalmente se utiliza para crear APIs para servicios web. Open API es una iniciativa que estandariza las APIs de REST. También puedes utilizar REST para diseñar tus URLs para vínculos directos.
Ten en cuenta que no estás creando un servicio web. Esta sección solo se enfoca en el diseño de URL.
Cómo diseñar las URLs
Primero, revisa las URLs de resultado en tu sitio web y, luego, interpreta qué representan en la app para Android:
/restaurants
enumera todos los restaurantes que administras./restaurants/:restaurantName
muestra los detalles de un solo restaurante./restaurants/:restaurantName/orders
muestra los pedidos de un restaurante./restaurants/:restaurantName/orders/:orderNumber
muestra un pedido específico en un restaurante./restaurants/:restaurantName/orders/latest
muestra el pedido más reciente en un restaurante.
Por qué el diseño de la URL es importante
Android tiene filtros de intents que controlan acciones de otro componente de apps y que también se utilizan para detectar URLs. Cuando defines un filtro de intents para detectar una URL, debes utilizar una estructura que dependa de prefijos de ruta de acceso y comodines directos. El siguiente es un ejemplo de cómo se estructura a partir de una URL existente en el sitio web de tu restaurante:
https://example.com/pawtato-3140-Skinner-Hollow-Road
Aunque esta URL especifica tu restaurante y su ubicación, la ruta de acceso puede generar un problema cuando se define un filtro de intents para que Android detecte la URL, ya que tu aplicación está basada en varias URLs de restaurante como estas:
https://example.com/rawrbucha-2064-carriage-lane
https://example.com/pizzabus-1447-davis-avenue
Cuando defines un filtro de intents con una ruta de acceso y un comodín para detectar estas URLs, puedes usar algo como https://example.com/*
, que cumplirá su objetivo. Sin embargo, no resolviste por completo el problema porque hay otras rutas de acceso existentes para distintas secciones en tu sitio web, como las siguientes:
Entrega: https://example.com/deliveries
Administración: https://example.com/admin
Quizás no sea conveniente que Android detecte estas URLs, ya que algunas de ellas podrían ser internas. Sin embargo, el filtro de intents https://example.com/*
definido las detectará, incluidas las URLs que no existen. Cuando un usuario hace clic en una de estas URLs, la URL se abrirá en el navegador (> Android 12) o podría aparecer un diálogo de desambiguación (< Android 12). En este diseño, no es el comportamiento esperable.
Ahora, Android ofrece prefijos de ruta de acceso que resuelven este problema, pero sí cambia el diseño de la URL, de:
https://example.com/*
a:
https://example.com/restaurants/*
La incorporación de una estructura de anidación jerárquica hace que tus filtros de intents estén claramente definidos, y Android detecta la URL que le indiques que detecte.
Prácticas recomendadas sobre el diseño de la URL
Estas son algunas prácticas recomendadas recopiladas a partir de Open API que se aplican a una perspectiva de vínculos directos:
- Enfoca el diseño de la URL en las entidades empresariales que exponen. Por ejemplo, en el comercio electrónico pueden ser clientes y pedidos. En el caso de viajes, pueden ser boletos y vuelos. En la app y el sitio web de tu restaurante, usarás restaurantes y pedidos.
- La mayoría de los métodos HTTP (GET, POST, DELETE, PUT) son verbos que describen la solicitud que se realiza, pero resultaría confuso utilizar verbos para los extremos en las URLs.
- Para describir colecciones, utiliza el plural de la entidad, como
/restaurants/:restaurantName
. De esta manera, la URL es más fácil de leer y mantener. El siguiente es un ejemplo con cada uno de los métodos HTTP:
GET /restaurants/pawtato
POST /restaurants
DELETE /restaurants
PUT /restaurants/pawtato
Resulta más fácil leer cada URL y entender lo que hace. Ten en cuenta que este codelab no abarca el diseño de API para servicios web ni qué hace cada método.
- Utiliza la anidación lógica para agrupar las URLs que contengan información relacionada. Por ejemplo, una URL para uno de nuestros restaurantes puede tener los pedidos en los que se está trabajando.
/restaurants/1/orders
4. Revisa los elementos de datos
El archivo AndroidManifest.xml
es una parte fundamental de Android. Describe la información de la app para las herramientas de compilación de Android, el SO Android y Google Play.
En el caso de los vínculos directos, debes definir un filtro de intents utilizando tres etiquetas principales: <action>
, <category>
y <data>
. En esta sección, nos enfocaremos principalmente en la etiqueta <data>
.
Un elemento <data>
le indica al SO Android la estructura de la URL de un vínculo una vez que el usuario hace clic en él. El formato y la estructura de la URL que puedes usar en el filtro de intents es el siguiente:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>|<pathAdvancedPattern>|<pathSuffix>]
Android lee, analiza y combina todos los elementos <data>
en un filtro de intents para que se tengan en cuenta todas las variaciones de los atributos. Por ejemplo:
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>
Android detectará las siguientes URLs:
http://example.com/restaurants
https://example.com/restaurants
http://example.com/restaurants/orders/*
https://example.com/restaurants/orders/*
Atributos de la ruta de acceso
path
(disponible en API 1)
Este atributo especifica una ruta de acceso completa (que comienza con /
) que se compara con la ruta de acceso completa en el intent. Por ejemplo, android:path="/restaurants/pawtato"
solo coincide con la ruta de acceso del sitio web /restaurants/pawtato
, mientras que si tenemos /restaurant/pawtato
, esta URL no coincide debido a la s
que falta.
pathPrefix
(disponible en API 1)
Este atributo especifica una ruta de acceso parcial que se compara solo con la parte inicial de la ruta de acceso del intent. Por ejemplo:
android:pathPrefix="/restaurants"
coincidirá con las rutas de acceso de "restaurants": /restaurants/pawtato
, /restaurants/pizzabus
, etcétera.
pathSuffix
(disponible en API 31)
Este atributo especifica una ruta de acceso que se compara exactamente con la parte final de la ruta de acceso en el intent. Por ejemplo:
android:pathSuffix="tato"
coincidirá con todas las rutas de acceso de "restaurants" que terminen con tato, como /restaurants/pawtato
y /restaurants/corgtato
.
pathPattern
(disponible en API 1)
Este atributo especifica una ruta de acceso completa que coincide con una ruta de acceso completa con comodines en el intent:
- Un asterisco (
*
) coincide con una secuencia compuesta por 0 a muchas repeticiones del carácter anterior. - Un punto seguido de un asterisco (
.*
) coincide con cualquier secuencia compuesta por 0 a muchos caracteres.
Ejemplos:
/restaurants/piz*abus
: Este patrón coincide con el restaurante de "Pizzabus", pero también coincidirá con restaurantes compuestos por 0 a más caracteresz
en el nombre, como/restaurants/pizzabus
,/restaurants/pizzzabus
y/restaurants/pizabus
./restaurants/.*
: Este patrón coincide con cualquier nombre de restaurante con la ruta de acceso/restaurants
, como/restaurants/pizzabus
y/restaurants/pawtato
, además de aquellos que la app no conoce, como/restaurants/wateriehall
.
pathAdvancePattern
(disponible en API 31)
Este atributo especifica una ruta de acceso completa que coincide con la ruta de acceso completa con patrones similares a una regex:
- Un punto (
.
) coincide con cualquier carácter. - Un par de corchetes (
[...]
) coincide con un rango de caracteres. Este par también admite el modificador no (^
). - Un asterisco (
*
) coincide con el patrón anterior 0 o más veces. - Un signo más (
+
) coincide con el patrón anterior 1 o más veces. - Las llaves (
{...}
) representan la cantidad de veces que un patrón podría coincidir.
Este atributo se puede considerar una extensión de pathPattern
. Brinda más flexibilidad sobre las URLs con las que debe coincidir, por ejemplo:
/restaurants/[a-zA-Z]*/orders/[0-9]{3}
coincide con cualquier pedido de restaurante que tenga hasta 3 dígitos de longitud./restaurants/[a-zA-Z]*/orders/latest
coincide con el último pedido de cualquiera de los restaurantes de la app.
5. Crea vínculos directos y vínculos web
Vínculos directos de esquema personalizado
Los vínculos directos con esquemas personalizados son el tipo más genérico de vínculo directo y los más fáciles de implementar, lo que trae inconvenientes. Los sitios web no pueden abrir estos vínculos. Cualquier app que declare en su manifiesto que admite ese esquema podrá abrir el vínculo.
Puedes usar cualquier esquema en el elemento <data>
. Por ejemplo, este codelab utiliza la URL food://restaurants/keybabs
.
- En Android Studio, agrega el siguiente filtro de intents al archivo de manifiesto:
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 verificar que tu aplicación pueda abrir vínculos con esquemas personalizados, cópiala en la pantalla principal y agrega lo siguiente a la actividad 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 probar que se reciba el intent, utiliza Android Debug Bridge (adb) con el siguiente comando:
adb shell am start -W -a android.intent.action.VIEW -d "food://restaurants/keybabs"
Este comando comienza un intent con la acción de VIEW y utiliza la URL suministrada como datos. Cuando ejecutas este comando, la app se lanza y recibe el intent. Observa los cambios en las secciones de texto de la pantalla principal. La primera sección muestra el mensaje Hello Android!, la segunda muestra a qué acción llamó el intent, y la tercera muestra a qué URL llamó el intent.
En la siguiente imagen, observa que en la sección inferior de Android Studio se ejecutó el comando adb
mencionado. A la derecha, la app muestra la información sobre el intent en la pantalla principal, lo que indica que se lo recibió.
Vínculos web
Los vínculos web son vínculos directos que usan http
y https
en lugar de esquemas personalizados.
Para la implementación de vínculos web, utiliza la ruta de acceso /restaurants/keybabs/order/latest.html
, que representa el último pedido que se recibió en el restaurante.
- Ajusta el archivo de manifiesto utilizando el filtro de intents existente.
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 se comparten ambas rutas de acceso (/restaurants/keybabs
), conviene ubicarlas dentro del mismo filtro de intents, ya que esto facilita la implementación y la lectura del archivo de manifiesto.
- Antes de probar el vínculo web, reinicia la app para aplicar los nuevos cambios.
- Utiliza el mismo comando adb para lanzar el intent, aunque, en este caso, actualizaremos la URL.
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/keybabs/orders/latest.html"
En la captura de pantalla, observa que, al recibirse el intent, el navegador web se abre para mostrar el sitio web, una función de las versiones posteriores a Android 12.
6. Configura Android App Links
Estos vínculos ofrecen la experiencia del usuario más fluida, ya que cuando un usuario hace clic en un vínculo, se garantiza que el vínculo lo llevará a la app sin un diálogo de desambiguación. Android App Links se implementó en Android 6.0 y son el tipo más específico de vínculo directo. Son vínculos web que utilizan el esquema http/https
y el atributo android:autoVerify
, que hace que la app sea el controlador predeterminado para cualquier vínculo coincidente. Los dos pasos principales para implementar Android App Links son los siguientes:
- Actualiza el archivo de manifiesto con el filtro de intents apropiado.
- Agrega la asociación con el sitio web para la verificación.
Cómo actualizar el archivo de manifiesto
- Para brindar compatibilidad con Android App Links, en el archivo de manifiesto, reemplaza la configuración anterior con lo siguiente:
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 intents agrega el atributo android:autoVerify
y lo establece en verdadero. Esto permite que el SO Android verifique el dominio cuando se instala la aplicación, y en cada actualización nueva.
Asociación con el sitio web
Para verificar un Android App Link, crea una asociación entre la aplicación y el sitio web. Es necesario publicar un archivo JSON de Vínculos de recursos digitales (DAL) de Google en el sitio web para que se produzca la validación.
Google DAL es un protocolo y una API que define sentencias verificables sobre otras apps y sitios web. En este codelab, crearás una sentencia sobre la app para Android en el archivo assetlinks.json
. A continuación, se muestra un ejemplo:
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 archivo puede almacenar una lista de sentencias, pero en este ejemplo se muestra solo un elemento. Cada sentencia debe incluir los siguientes campos:
- Relación: Describe una o más relaciones declaradas sobre el objetivo.
- Objetivo: El recurso al que se aplica esta sentencia. Puede ser uno de dos objetivos disponibles:
web
oandroid_app
.
La propiedad target
de la sentencia de Android incluye los siguientes campos:
namespace
: Laandroid_app
para todas las apps para Androidpackage_name
: El nombre del paquete completamente calificado (com.devrel.deeplinksbasics
)sha256_cert_fingerprints
: La huella digital del certificado para la app (aprenderás cómo generar este certificado en la próxima sección)
Huella digital del certificado
Hay varios métodos para obtener la huella digital del certificado. Este codelab utiliza dos métodos: uno para la compilación de depuración de la aplicación y el otro para ayudar a lanzar la app en Google Play Store.
Configuración de depuración
La primera vez que Android Studio ejecute tu proyecto, firmará automáticamente la app con un certificado de depuración. La ubicación de este certificado es: $HOME/.android/debug.keystore
. Puedes usar un comando de Gradle para obtener esta huella digital del certificado de SHA-256. Los pasos que debes seguir son los siguientes:
- Presiona dos veces
Control
y debería aparecer el menú Run anything. Si no aparece, puedes encontrarlo en el menú de Gradle de la barra lateral derecha y, luego, hacer clic en el ícono de Gradle.
- Escribe
gradle signingReport
y presionaEnter
. El comando se ejecuta en la consola y muestra la información sobre la huella digital de la variante de la app de depuración.
- Para completar la asociación con el sitio web, copia la huella digital del certificado de SHA-256, actualiza el archivo JSON y súbelo a tu sitio web en la ubicación
https://<domain>/.well-know/assetlinks.json
. Esta entrada de blog sobre Android App Links te ayudará a configurarla. - Si tu app sigue ejecutándose, presiona Stop para detenerla.
- Para volver a lanzar el proceso de verificación, quita la app del simulador. En el simulador, mantén presionado el ícono de la app de DeepLinksBasics y selecciona App Info. Haz clic en Uninstall y, luego, en Confirm en el modal. A continuación, ejecuta la aplicación para que Android Studio pueda verificar la asociación.
- Asegúrate de seleccionar la configuración en ejecución app. De lo contrario, el informe de firma de Gradle volverá a ejecutarse.
- Reinicia la aplicación y lanza el intent con la URL de Android App Link:
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/"
- Observa que se lanza la app y el intent aparece en la pantalla principal.
Felicitaciones, ¡acabas de crear tu primer Android App Link!
Configuración de lanzamiento
Ahora, para poder subir tu aplicación con Android App Links a Play Store, debes usar una compilación de lanzamiento con la huella digital del certificado apropiada. Para generarla y subirla, sigue esto pasos.
- En el menú principal de Android Studio, haz clic en Build > Generate Signed Bundle/APK.
- En el siguiente diálogo, selecciona Android App Bundle para la firma de apps de Play o APK si estás realizando la implementación a un dispositivo directamente.
- En el siguiente diálogo, en Key store path, haz clic en Create new. Se abrirá una nueva ventana.
- Selecciona una ruta de acceso para tu almacén de claves y asígnale el nombre
basics-keystore.jks
. - Crea y confirma una contraseña para el almacén de claves.
- Deja el valor predeterminado para el alias de la clave.
- Asegúrate de que la contraseña y la confirmación sean las mismas que en el almacén de claves. Deben coincidir.
- Completa la información sobre el certificado y haz clic en OK.
- Asegúrate de que la opción para exportar las claves encriptadas esté marcada para la firma de apps de Play y haz clic en Next.
- En este diálogo, selecciona la variante de compilación de lanzamiento y haz clic en Finish. Ahora puedes subir la app a Google Play Store y utilizar la firma de apps de Play.
Firma de apps de Play
Con la firma de apps de Play, Google te ayuda a administrar y proteger la clave de firma de tu app. Solo debes subir el paquete firmado de la app, que creaste en el paso anterior.
Para recuperar la huella digital del certificado del archivo assetlinks.json
y tener tus Android App Links en la compilación de la variante de lanzamiento, sigue estos pasos:
- En Google Play Console, haz clic en Create app.
- Escribe Deep Links Basics como nombre de la app.
- Selecciona App y Free en las siguientes dos opciones.
- Acepta las Declarations y haz clic en Create app.
- Para subir el paquete y probar los Android App Links, en el menú de la izquierda, selecciona Testing > internal testing.
- Haz clic en Create new release.
- En la siguiente pantalla, haz clic en Upload y, luego, selecciona el paquete generado en la última sección. Encontrarás el archivo
app-release.aab
en DeepLinksBascis > app > release. Haz clic en Open y espera hasta que se suba el paquete. - Una vez que termine de subirse, deja el resto de los campos con los valores predeterminados por ahora. Haz clic en Save.
- Para prepararte para la próxima sección, haz clic en Review release y, luego, en la próxima pantalla, selecciona Start rollout to Internal testing. Ignora las advertencias, ya que la publicación en Play Store no está dentro del alcance de este codelab.
- Haz clic en Rollout en el modal.
- Para obtener la huella digital del certificado SHA-256 que creó la firma de apps de Play, ve a la pestaña Deep links en el menú de la izquierda y, luego, consulta el panel de vínculos directos.
- En la sección Domains, haz clic en el dominio del sitio web. Google Play Console te indicará que no validaste el dominio con tu app (asociación con el sitio web).
- En la sección Fix Domain Issues, haz clic en la flecha Show More.
- En esta pantalla, Google Play Console muestra cómo actualizar el archivo
assetlinks.json
con la huella digital del certificado. Copia el fragmento de código y actualiza el archivoassetlinks.json
.
- Una vez que el archivo
assetlinks.json
esté actualizado, haz clic en Recheck verification. Si todavía no se completó la verificación, espera hasta cinco minutos para que el servicio de verificación detecte nuevos cambios. - Si vuelves a cargar la página del panel Deep links verás que no habrá más errores de verificación.
Verificación de la app de carga
Ya sabes cómo verificar una app alojada en el simulador. Ahora, aprenderás a verificar una app que se cargó a Play Store.
Para instalar la aplicación en el emulador y asegurarte de que el Android App Link esté verificado, sigue estos pasos:
- En la barra lateral de la izquierda, haz clic en Releases Overview y, luego, selecciona la versión más reciente que acabas de subir. Debería ser la versión 1 (1.0).
- Haz clic en Release details (flecha azul derecha) para ver los detalles de la versión.
- Haz clic en el mismo botón de la flecha azul derecha para obtener información sobre el paquete de aplicación.
- En este modal, selecciona la pestaña Downloads y, luego, haz clic en download en el recurso Signed, universal APK.
- Antes de instalar este paquete en el simulador, borra la aplicación anterior que instaló Android Studio.
- En el simulador, mantén presionado el ícono de la app de DeepLinksBasics y selecciona App Info. Haz clic en Uninstall y, luego, en Confirm en el modal.
- Para instalar el paquete descargado, arrastra y suelta el archivo
1.apk
en la pantalla del simulador y espera a que se instale.
- Para probar la validación, abre el terminal en Android Studio y ejecuta el proceso de verificación con los siguientes dos comandos:
adb shell pm verify-app-links --re-verify com.devrel.deeplinksbasics
adb shell pm get-app-links com.devrel.deeplinksbasics
- Después del comando
get-app-links
, deberías ver un mensajeverified
en la consola. Si ves un mensajelegacy_failure
, asegúrate de que la huella digital del certificado coincida con la que subiste para el sitio web. Si coincide y sigues viendo el mensaje de verificación, intenta completar los pasos 6, 7 y 8 nuevamente.
7. Implementa Android App Links
Ahora que ya tienes todo configurado, es momento de implementar la app.
Para la implementación, utilizaremos Jetpack Compose. Si deseas obtener más información sobre Jetpack Compose, consulta Compila mejores apps más rápido con Jetpack Compose.
Dependencias de código
Para incluir y actualizar algunas dependencias que necesitas para este proyecto, completa este paso:
- Además, agrega lo siguiente a los archivos de Gradle
Module
yProject
:
build.gradle (proyecto)
buildscript {
...
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:2.43"
}
}
build.gradle (módulo)
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"
}
El archivo project zip incluye una directorio de imágenes con 10 imágenes libres de regalías que se pueden utilizar para cada restaurante. No dudes en utilizarlas o incluye tus propias imágenes.
Para agregar el punto de entrada principal para HiltAndroidApp
, completa este paso:
- Crea un nuevo archivo o clase de Kotlin con el nombre
DeepLinksBasicsApplication.kt
y, luego, actualiza el archivo de manifiesto con el nuevo nombre de aplicación.
DeepLinksBasicsApplication.kt
package com.devrel.deeplinksbasics
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class DeepLinksBasicsApplication : Application() {}
AndroidManifest.xml
<?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"
...
Datos
Debes crear una capa de datos para los restaurantes con una fuente de datos locales, un repositorio y una clase Restaurant
. Todo estará alojado en un paquete data
que deberás crear. Para hacerlo, sigue estos pasos:
- En el archivo
Restaurant.kt
, crea una claseRestaurant
con el siguiente fragmento de código:
Restaurant.kt
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
)
- En el archivo
RestaurantLocalDataSource.kt
, agrega algunos restaurantes en la clase de fuente de datos. No te olvides de actualizar los datos con tu propio dominio. Consulta el siguiente fragmento de código:
RestaurantLocalDataSource.kt
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,
),
)
}
- Recuerda importar las imágenes a tu proyecto.
- A continuación, en el archivo
RestaurantRepository.kt
, agrega el repositorioRestaurant
con una función para encontrar un restaurante por su nombre, como en el siguiente fragmento de código:
RestaurantRepository.kt
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 poder seleccionar un restaurante a través de la app y un Android App Link, debes crear un ViewModel
que cambie el valor del restaurante seleccionado. Completa el siguiente paso:
- En el archivo
RestaurantViewModel.kt
, agrega el siguiente fragmento de código:
RestaurantViewModel.kt
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
Ahora que tienes la lógica del viewmodel y las capas de datos, es momento de agregar una capa de IU. Gracias a la biblioteca de Jetpack Compose, puedes hacerlo en pocos pasos. Para esta app, te recomendamos renderizar los restaurantes en una grilla de tarjetas. El usuario puede hacer clic en cada tarjeta para ver los detalles del restaurante. Necesitas tres funciones de componibilidad principales y un componente de navegación que enrute al restaurante correspondiente.
Para agregar una capa de IU, sigue estos pasos:
- Comienza con la función de componibilidad que renderiza los detalles de cada restaurante. En el archivo
RestaurantCardDetails.kt
, agrega el siguiente fragmento de código:
RestaurantCardDetails.kt
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}")
}
}
}
}
- A continuación, implementa la celda de cuadrícula y la cuadrícula. En el archivo
RastaurantCell.kt
, agrega el siguiente fragmento de código:
RestaurantCell.kt
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}")
}
}
}
- En el archivo
RestaurantGrid.kt
, agrega el siguiente fragmento de código:
RestaurantGrid.kt
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)
}
}
}
}
}
- A continuación, debes implementar el estado de la aplicación y la lógica de navegación y, luego, actualizar
MainActivity.kt
. Puede dirigir a un restaurante específico cuando un usuario hace clic en una tarjeta de restaurante. En el archivoRestaurantAppState.kt
, agrega el siguiente fragmento de código:
RestaurantAppState.kt
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 la navegación, debes crear el
NavHost
y usar las rutas componibles para dirigir a cada restaurante. En el archivoRestaurantApp.kt
, agrega el siguiente fragmento de código:
RestaurantApp.kt
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)
}
}
}
- Ahora ya puedes actualizar
MainActivity.kt
con la instancia de aplicación. Reemplaza el archivo con el siguiente código:
MainActivity .kt
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()
}
}
}
}
}
- Ejecuta la aplicación para navegar a la cuadrícula y selecciona un restaurante en particular. Verás que, cuando seleccionas un restaurante, la app muestra ese restaurante y sus detalles.
Android App Links
Ahora, agrega Android App Links a la cuadrícula y a cada restaurante. Ya tienes la sección AndroidManifest.xml
para la cuadrícula en /restaurants
. La ventaja es que puedes usar la misma cuadrícula para todos los restaurantes. Debes agregar una nueva configuración de ruta a tu lógica. Para hacerlo, sigue estos pasos:
- Actualiza el archivo de manifiesto con el filtro de intents para recibir
/restaurants
como una ruta de acceso y recuerda incluir tu dominio como host. En el archivoAndroidManifest.xml
, agrega el siguiente fragmento de código:
AndroidManifest.xml
...
<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>
- En el archivo
RestaurantApp.kt
, agrega el siguiente fragmento de código:
RestaurantApp.kt
...
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
)
}
}
}
}
De forma interna, el NavHost
coincide con los datos de Uri
del intent de datos con las rutas componibles. Cuando una ruta coincide, se renderiza composable
.
El componente composable
acepta un parámetro deepLinks
, que contiene una lista de las URIs que se recibieron del filtro de intents. En este codelab, agregarás la URL del sitio web creado y definirás el parámetro de ID para recibir y enviar al usuario a ese restaurante en particular.
- Para asegurarte de que la lógica de la app envíe a un usuario al restaurante correspondiente después de hacer clic en un Android App Link, utiliza
adb
:
adb shell am start -W -a android.intent.action.VIEW -d "https://sabs-deeplinks-test.web.app/restaurants/gyrosAndMoar"
Observa que la app muestra el restaurante correspondiente.
8. Consulta el panel de Play Developer Console
Ya conoces el panel de vínculos directos. Este panel brinda toda la información que necesitas para garantizar que los vínculos directos funcionen correctamente. Incluso puedes ver cómo será la versión para apps. Podrás ver los dominios, vínculos y vínculos personalizados que se agregaron en el archivo de manifiesto. También aprenderás dónde actualizar el archivo assetlinks.json
en caso de problemas.
9. Conclusión
Felicitaciones, creaste correctamente tu primera aplicación de Android App Links.
Ahora conoces el proceso necesario para diseñar, configurar, crear y probar tus Android App Links. Este proceso tiene diferentes etapas. Por eso, este codelab agrupa todos estos detalles para que puedas desarrollar correctamente con el SO Android.
Ahora conoces los pasos clave para que funcionen los Android App Links.
Lecturas adicionales
- Introducción a vínculos directos
- Vínculos directos, conviértete en héroe de la noche a la mañana
- Soluciona los problemas de tus vínculos directos