Ir al contenido principal

Abrir links con aplicaciones nativas y no el browser (deeplinking)

El problema que tengo con algunas aplicaciones Android/iOS es que cuando recibo un link por algún medio (mail, tweet, etc) al abrirlo me lo abre con el browser, en lugar de abrirlo con una aplicación nativa asociada a ese “contenido”.

Por ejemplo, si recibo un link a un tweet espero que lo abra con alguna aplicación de twitter que tenga instalada y no con el browser. De modo análogo si recibo un mail con una nota de prensa de un medio X y tengo la aplicación de ese medio X instalada, espero que el link lo abra con la aplicación nativa y no con el browser.
Lo mismo quisiera con mi aplicación de "banking" o cualquiera que tenga instalada y sepa manejar ese "contenido" (link).

Los motivos son bastante obvios pero los resumo en: la experiencia de usuario es mucho mejor en la aplicación nativa que en el navegador.

Parte importante del tema es que el mismo link sea válido tanto para ver el contenido en el browser como para verlo en la aplicación, porque como proveedor de contenido no sé si los consumidores tendrán o no la aplicación instalada.

Una funcionalidad que se incluyó en Genexus 15 U6 que me resultó interesante es la posibilidad de hacer “deeplinking” en aplicaciones SD , lo cual resuelve este escenario.



¿Cómo resolverlo en GeneXus?

Supongamos que tengo una aplicación que manejo contenidos de un Evento (Sesiones, Oradores, etc).
Para ir a ver la lista de Conferencias el link sería algo como: https://www.genexus.com/Gx27/ConferencesList

¿Cómo asocio ese link con una aplicación nativa SD?

En el Upgrade 6 de GeneXus 15 definiendo dos propiedades en los objetos SD es suficiente para asociar esos links y lograr que los mismos abran la aplicación.

Deep Link base URL 
En esta defino qué URLs “sabe abrir” mi aplicación. Se define a nivel de main y el valor ahí es el “servidor/directorio virtual” de mi aplicación (en el ejemplo: https://www.genexus.com/Gx27/). Se pueden especificar varios valores separados por ";".

Deep Link Name
Esta la defino a nivel de cada objeto de la aplicación que quiera sea alcanzado por un link y el valor en el ejemplo sería: “ConferencesList”. Otro panel tendrá el valor “SpeakersList” y así con todos los paneles que quiero sean “linkeados” desde afuera (deeplinking).

KB con la implementación

Si ya está cansado de leer y prefiere explorar la base de conocimientos: la puede bajar de aquí 
Una vez bajada se puede generar con Genexus Evolution 15 Upgrade 6, probarla directamente en su ambiente, revisar cómo está hecha, etc.

Nota: en mi caso utilicé el generador .NET y Android para el cliente. Es equivalente en otras plataformas (Java, iOs, etc).

Es un ejemplo muy sencillo donde uso las propiedades antes mencionadas y algunas cosas más que pueden servir, las cuales comento más adelante en este articulo.

Ejemplo básico 

Como decía: definiendo esas dos propiedades es suficiente para que el esquema funcione en un ejemplo básico.

En mi caso tengo una aplicación web que muestra una lista de speakers y una lista de conferencias 

A su vez tengo una aplicación Android con un panel para ver los oradores y otro para ver las sesiones.

Para que funcionen los “deep links” simplemente configuro en mi app SD:

Paso 1: qué URLs sabe manejar mi aplicación
En el main de mi SD application la propiedad “Deep Link Base URL” con el valor: “http://labsnet.genexuscloud.com/DeepLink/”




Se puede ver en el objeto:SD.SuperPuff de la KB mencionada antes.

Paso 2: qué objeto resuelve qué link
En el panel: SD.SpeakersList configuré la propiedad “Deep Link Name” con el valor “gx:Web.SpeakersList” (*)



(*) El prefijo "gx:" que indica que es un objeto GX de la misma KB. De este modo si se renombra el objeto el valor sigue siendo válido. Podría incluirse un string cualquiera que corresponda al nombre del objeto o incluso más de un objetos separados por ";"

En ejecución
Luego en ejecución supongamos que recibo un mail promocionando el Encuentro:



Selecciono uno de esos links y, como tengo la aplicación correspondiente instalada, me ofrece abrir el link con el browser o con la aplicación correspondiente:



Eligiendo la aplicación "Super Puff" abre dicha aplicación directamente en el panel que muestra la lista de Speakers:


De modo análogo si elijo el link de “Sessions List” voy a ver la lista de conferencias:



En cualquier caso quedo navegando dentro de la aplicación, pudiendo ir a ver el detalle de cada conferencia o lo que la aplicación provea como funcionalidad:



Por el contrario, si elijo esos links y prefiero abrirlos con el navegador (Chrome en el ejemplo), entonces abrirá la página web correspondiente:



Es decir: el mismo link sirve para mostrar la información en el browser o en la app.

Nota: si quisiera que la URL la abriera con la aplicación directamente sin preguntar al usuario se puede poner un archivo en el server que evita esa pregunta. En Android es opcional, en IOS ese archivo es mandatorio. Más info

¿Qué pasa si mis objetos reciben parámetros?

En el ejemplo básico eran paneles que no recibían parámetros de ningún tipo (lista de Speakers, lista de Conferencias), ahora, podría querer ir directamente a una Conferencia o Speaker en particular en cuyo caso preciso especificar el “SpeakerId” o “SessionId” como parámetro. ¿cómo lo resuelvo?

Los parámetros son manejados automáticamente por posición como están en la regla parm del objeto que resuelve el link. Se manejan tanto por posición (separados por ",") como nominados (separados por "&" y con forma "<parm>=<val>").

Siguiendo con el ejemplo para ir directamente a ver la información de un speaker específico solo hace falta incluir el “Deep Link Name” en el panel que permite visualizar el speaker y los parámetros se resolverán automaticamente.

Al igual que en el ejemplo básico visto antes: configuro en el Deep link name el nombre del objeto y nada más:



Como ese panel (SD.ViewSpeaker) recibe como parámetro un numérico (SpeakerId) entonces automaticamente se tomará de la URL el valor que sigue después del signo “?” como tal.

En ejecución
Recibo un mail del estilo:


Al abrir el link con la aplicación voy directamente al panel que me muestra la información de ese speaker (SpeakerId=30):




El mismo link pero si elijo abrirlo en el browser:



¿Qué pasa si mi escenario es mucho más complicado que eso?

Sí, a veces sucede que la vida es un poco más complicada, a diario trabajamos en GeneXus para que la vida de los desarrolladores sea más sencilla y la de sus clientes más aún, pero pasa.

Si el escenario es más complicado, por ejemplo: el desarrollo web está en otra base de conocimientos, está con otra versión de Genexus, no lo hice yo y no está a mi alcance modificarlo, el mismo maneja otra serie de objetos y/o parámetros, etc, etc: igual hay una solución!

Para todos esos casos más complejos existe un esquema bastante sencillo que es recibir la URL y “parsearla” para definir a qué objeto de mi aplicación invocar en cada caso y con qué parámetros hacerlo.

Esto se basa en un external object que se provee con Genexus llamado Deeplink, con el cual se puede programar en el objeto main algo similar a:


De este modo busco en la URL nombre del objeto y en base al mismo (o al algoritmo que quiera) determino qué objeto llamar de mi aplicación, se obtienen los parámetros, etc.
Esto se puede ver en el objeto SD.SuperPuffManual

En mi caso si el link no entra por ninguna de las opciones “esperables” (sale por el "otherwise" entonces me quedo en el objeto main (SD.SuperPuffManual()).


¿Para que sirve el parámetro handled?
Indica si el link fue “manejado” o no por la aplicación. Si fue manejado (true) entonces no se hace nada más. Si no fue manejado (false) entonces se hace un “fallback” al browser (disponible en V15 U6 para iOS y a partir de Genexus U7 para Android).

Es decir, se interpreta que la aplicación no es capaz de manejar el link y por lo tanto decide enviarlo al browser.

Hágalo ud mismo!

Como mencioné al principio, la Base de Conocimiento está disponible aquí para quien quiera usarla. Tiene varios objetos mains que van sobre los mismos paneles para probar el comportamiento manual o parseando el developer, online u offline, etc pero ahí está.

UPDATE 04/09

A partir de V15U7 se agregaron un par de features interesantes:
1. Se puede usar más de un "deep link name" en el mismo panel. 
2. En lugar de un texto libre (*) se puede elegir un objeto de la propia KB con lo cual es más práctico y queda la referencia a dicho objeto explicita en la KB.

(*) debe coincidir exactamente con el nombre el objeto, incluyendo ASPX si es .NET o el package si es Java. Esto, si se referencia al objeto de la KB, se resuelve automaticamente.

Más detalles aqui

UPDATE 06/10

En el cliente de twitter de Android el link que pueda venir en un tweet no se abre directamente en el navegador sino en un "webview" del cliente de twitter (al menos eso parece), con lo cual en realidad nunca le llega al sistema operativo el pedido de "abrir TAL link". Como ese pedido no llega entonces no brinda la posibilidad de abrirlo con la propia aplicación y el deeplinking no funciona.

No he encontrado modo de resolverlo, en cualquier caso si el link se abre con la opcion que aparece en el webview de "open in chrome", ahí funciona correctamente.
Lamento pero por el momento no encontré solución.

UPDATE 05/04/18

Actualicé la KB a V15U9 y cambié un poco el post en cuanto a imagenes, dejé la app en produccion (labsnet en lugar de apps5)

FAQ

¿Funciona en aplicaciones offline?
Sí y es parte de la gracia porque si no tengo conexión igual abre la aplicación con el contenido correspondiente (si la DB de la aplicación está actualizada obviamente)

¿Funciona con los acortadores de URLs?
No directamente. En ese caso no se reconoce la URL por lo cual la aplicación no la abrirá. 
Este escenario se resuelve (no lo probé) con meta-tags HTML en el servidor (https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html) de modo que una vez abierta la página con el browser, aparezca la opción de abrirla con la aplicación (o incluso instalar la aplicación).

Requiere un paso más, requiere tener conexión de datos pero es una opción a manejar combinada con el deeplinking.

¿Se pueden combinar ambos mecanismos (manual y automático)? 
Sí, primero aplica el método "manual" si existe y luego el "automático" (si el manual no resolvió la URL).

Modo manual: se usa el external object (deeplink) para tratar de resolver el link
Modo automático: se mapea el Link con un panel que tenga el Deep Link Name correspondiente (como se explicó al principio).

De modo combinado funcionan así: primero aplica el "manual" (si existe) y si este no lo pudo resolver (termina con &handled=false) entonces intenta el modo "automático" (ver si lo puede resolver en base a los Deep Link Name).

Si ninguno de los algoritmos lo resuelve entonces termina en el fallback al navegador.

Comentarios

Entradas más populares de este blog

¡A la salud de mi KB!

Es bueno, especialmente en "bases de conocimiento" (KB) que han pasado por varias versiones de Genexus, chequear su "estado de salud". En este sentido KBDoctor  es una herramienta que ayuda mucho, principalmente desde el punto de vista del "modelo" Genexus (atributos, calls, definiciones de variables, etc) representado en una KB. También es útil revisar la salud de los archivos que lo soportan. Hasta la 9.0 eran archivos C-tree (los famosos .DAT) que tenían indices (los famosos .IDX) y teníamos en "rebuild -y" que mejoraba esos archivos y sobre todo reconstruía los indices. A partir de la X las KBs se almacenan en MS SQL Server por lo cual la administración de la misma pasó de ser un "file server" a un "database server". En este sentido algo que me ha dado muy buenos resultados es el "CheckKnowledgeBase".

Rocha:Constantes tipo fecha

En la Rocha se soportan constantes del tipo fecha o fecha-hora con formato ANSI/ISO (AAAA-MM-DD HH:MM:SS).  Tecnicamente (Sintáxis): <date>::=    [0-9]{1,4}"/"[0-9]{1,2}"/"[0-9]{1,2} | [0-9]{1,4}"."[0-9]{1,2}"."[0-9]{1,2} | [0-9]{1,4}"-"[0-9]{1,2}"-"[0-9]{1,2} <hms>::=    [0-9]{1,2}[ap] | [0-9]{1,2}":"[0-9]{1,2}[ap]? | [0-9]{1,2}":"[0-9]{1,2}":"[0-9]{1,2}[ap]? <constant> ::=   "#"<date>"#" | "#"<date> <hms>"#" | "#"<hms>"#" Funcionalmente Se pueden utilizar esas constantes en las reglas, eventos, propiedades, etc (todo lugar donde se utilice el parser): Algunos ejemplos básicos: &FechaInicial=#2007-01-01# &FechaHoraInicial=#07-1-1 11:15a# &HoraInicial=#11a# Me parece bueno no tener que escribir funciones (CTOD, TTOC) sobre constantes tipo char para lograr una fecha y mucho mejor aun en