Integración con Android
A continuación se muestra cómo se ha organizado la generación del análisis.
Configuración básica
Respecto a las empresas del sector, he encontrado una que tiene un asistente de integración. En concreto AppsFlyer. Creo que EMMA podría hacer algo similar para facilitar todo el proceso de integración a los desarrolladores.
Integración y gestión de dependencias
Comenzamos la integración del SDK siguiendo las indicaciones de la documentación. Para ello, creamos un proyecto en Android Studio con la propiedad Empty Views Activity, en Kotlin, con el SDK mínimo API 24 (“Nougat”, Android 7.0 ) y usando Kotlin DSL.
Al introducir el repositorio, no se especifica en qué bloque debe de ir. Dado que es un repositorio, creo que deberíamos de indicarlo de la siguiente forma:
dependencyResolutionManagement {
repositories {
maven("https://repo.emma.io/emma")
}
}
Quizás, para desarrolladores poco experimentados sea más claro especificar todas las etiquetas padre. Además, veo que la
documentación menciona el repositorio con el formato de bloque. Si solo se incluye el repositorio y no se configura nada
más, podría sustituirse por la versión simplificada (la que he puesto). También creo que hay que corregir el párrafo
donde se menciona este repositorio, puesto que hace mención al fichero build.gradle
y, en realidad, iría en settings.gradle
.
A continuación, respecto a las dependencias que se mencionan, considero que se podría utilizar la sintaxis de versiones de catálogo. Es una recomendación que hace el IDE y se simplifica mucho la sintaxis. Si se usa la versión de catálogo es cierto que hay que mostrar la configuración del fichero libs.versions.toml y puede ser más engorroso. Además, convendría eliminar el uso de “+” en las invocaciones de dependencias, puede resultar inestable según nos recomienda el IDE. Por lo que cambiaría el código a la siguiente forma:
dependencies {
implementation(libs.emmasdk)
}
[version]
emmasdk = "4.15.4"
[libraries]
emmasdk = { module = "io.emma:eMMaSDK", version.ref = "emmasdk" }
A continuación, en el epígrafe de integración básica se menciona la clave de sesión pero no se utiliza hasta varios epígrafes después. Así que no lo mencionaría hasta entonces.
Ofuscación
Creo que el apartado actual de ProGuard que hay actualmente, se puede renombrar por Ofuscación y ponerlo dentro del apartado Integración básica. A pesar de esto y, en comparación con otras fuentes de documentación, las reglas que se proporcionan son relativamente claras. Si bien es cierto que no se menciona nada sobre R8 (la herramienta de ofuscación de Android), pero en términos generales creo que está bien este apartado.
Referencias en la instalación
En la documentación de integración no se observa ningún punto donde se mencionen las referencias de instalación, como sí hacen Adjust y AppsFlyer.
Permisos requeridos
Respecto a los permisos requeridos del SDK, aquellos que trae por defecto no los incluiría dentro de un bloque de código. Simplemente, los mencionaría. Quizás, ver el bloque de código induce a no leer el párrafo de arriba y directamente copiar el código. Solo dejaría el bloque de código que es necesario poner.
Además, en el apartado se incluye por defecto la identificación de anuncios, pero no se menciona la Ley de Protección de la Privacidad en Línea para Niños (COPPA) como sí se hace en la documentación de Adjust o AppsFlyer.
Integración de Huawei
En cuanto a la sección de Huawei, parece que su configuración tiene varios pasos más que otros proveedores. Por la disposición de la documentación, me ha costado identificar qué partes son de Huawei y cuáles son genéricas para Android. Sobre todo las primeras veces que consulté la documentación. Quizás estaría bien separar la implementación de Huawei en una página a parte (donde se explique toda la implementación desde cero) o bien poner un bloque de texto (de un color diferente al fondo de la documentación) desplegable.
También se usan direcciones URL con protocolo HTTP que Android Studio las reconoce como inseguras. Ante esto tenemos dos opciones, seguir usando la URL insegura o usar URL HTTPS. A continuación, se muestra un ejemplo explicativo con las líneas necesarias.
Configuración de conexión HTTP y HTTPS
Se debe incluir una instrucción específica para permitir a Gradle la conexión con repositorios inseguros. Es útil para repositorios que no dispongan de conexión HTTP. En la documentación no se menciona nada de esta configuración y da error al construir el proyecto.
buildscript {
repositories {
google()
mavenCentral()
maven {
url = uri("http://developer.huawei.com/repo/")
isAllowInsecureProtocol = true
}
}
}
Se modifica la URL para que sea HTTPS, evita usar configuraciones como las del caso anterior.
buildscript {
repositories {
google()
mavenCentral()
maven("https://developer.huawei.com/repo/")
}
}
En el bloque de código, no se especifica qué fichero build es donde van las dependencias y no se usan versiones de catálogo, por lo que sugiero cambiarlo a algo como así:
Se puede observar que no se incluye el bloque allprojects. Este bloque ya no se usa, así que lo he eliminado.
buildscript {
repositories {
google()
mavenCentral()
maven("https://developer.huawei.com/repo/")
}
dependencies {
classpath(libs.gradle)
classpath(libs.agcp)
}
}
Importante poner bien las versiones o, en su defecto, poner “latest version”. Con eso evitamos errores a la hora de construir el proyecto y que pueda creerse que se deben a otras cosas.
[version]
agcp = "1.9.1.301"
gradle = "8.9.1"
[libraries]
agcp = { module = "com.huawei.agconnect:agcp", version.ref = "agcp" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
Respecto al fichero de dependencias de la aplicación, se usa una sintaxis para aplicar el plugin, que actualmente no es válida. La línea indicada en la documentación tiene que ir dentro de otro bloque. Tal y como se indica a continuación.
plugins {
id("com.huawei.agconnect")
}
dependencies {
// other dependencies
implementation 'com.huawei.hms:ads-identifier:3.4.62.300'
implementation(libs.ads.identifier)
}
apply plugin: 'com.huawei.agconnect'
[versions]
adsIdentifier = "3.4.62.300"
[libraries]
ads-identifier = { module = "com.huawei.hms:ads-identifier", version.ref = "adsIdentifier" }
Considero que puede ser útil mencionar o recomendar la consulta de las últimas versiones de las dependencias usadas, con el fin de no generar conflictos. A lo largo de la documentación se usan muchas dependencias, por lo que se podría usar un apartado de “Referencias” o similar para listar todas las dependencias y un enlace a su historial de versiones. Un ejemplo sería la versión del SDK de AGCP y Gradle.
Inicialización de la librería
En este punto, no hay ninguna explicación de por qué se proporciona directamente el código de la clase Application. Es decir, si comparamos con la documentación de AppsFlyer, se explica que el SDK se puede iniciar desde la clase Application (para un inicio inmediato) o desde la clase Activity (para un inicio aplazado), así como los motivos. En el apartado de inicio aplazado del SDK, no se explica tampoco.
Respecto al inicio aplazado, en este punto, no se explica la razón por la cual se debe de aplazar el inicio del SDK. Si lo comparamos con la documentación de AppsFlyer, en su documentación sí tienen un pequeño párrafo en el que explican la razón por la que se tendría que aplazar el inicio, que es por políticas de privacidad.
Privacidad
En comparación con otras fuentes de documentación (estrategia de preservación de la privacidad y métodos de preservación de privacidad, la integración en Android no tiene un apartado de privacidad dedicado. El apartado que más se le asemeja es el de Manifiesto de la Privacidad, pero tiene el icono de Apple y su contenido está mezclado con varias plataformas (entre las cuales no encuentro Android).
Powlinks
A la hora de configurar los enlaces en la plataforma, la explicación de los fingerprints tiene un enlace “Saber más”. Ese enlace te lleva a la documentación de la plataforma, pero no a un apartado en concreto. En cuanto a la implementación, no se ha podido testear la funcionalidad al completo porque la aplicación no la he subido a Google Play. Sí he podido generar tanto la campaña como la fuente de la campaña y, dentro del emulador, ver cómo aparece una página de Google Play con el nombre de mi aplicación. Al pulsar, me redirige a la URL que tengo configurada en la campaña.


Dentro de la campaña, he creado dos fuentes de medios. La configuración de dichas fuentes se muestra en las siguientes capturas:




Lo que ocurre es que, al no tener mi app subida, los enlaces no funcionan correctamente. Dentro de la campaña tengo la URL de WhatsApp, pero en cada fuente de medios tengo configurado un enlace a la pantalla de los Cupones dentro de mi app. No he encontrado otra forma de probar el funcionamiento. Me ha costado un poco entender cómo configurar todo el “ecosistema” de los powlinks para hacer una prueba rápida.
Respecto a la integración, como el bloque de código del fichero XML es tan grade, yo especificaría qué líneas han de modificarse y qué valor nuevo deberían tomar. Esta cuestión se puede hacer segmentando el fichero en bloques de código más pequeños o poniendo el fichero entero y resaltar las líneas a modificar (junto con una pequeña explicación). Un ejemplo sería el siguiente:
<activity
android:name="io.emma.android.activities.EMMADeepLinkActivity"
android:noHistory="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="{YOUR_DEEPLINK_SCHEME}"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="subdomain.powlink.io"
android:scheme="https"/>
<data
android:host="shortsubdomain.pwlnk.io"
android:scheme="https"/>
</intent-filter>
<!-- Metadatos que indican la actividad encargada de gestionar los Deeplinks -->
<meta-data
android:name="io.emma.DEEPLINK_OPEN_ACTIVITY"
android:value="com.your.package.CustomDeeplinkActivity"/>
</activity>
Los subdominios que se resaltan deben obtenerse de aquellos que se han configurado en la
plataforma, manteniendo
las partes .powlink.io
y pwlnk.io
sin modificar. Luego, tras modificar los subdominios
Quedaría más claro mencionar la modificación de las siguientes líneas: El esquema de tu enlace lo sustituyes
por YOUR_DEEPLINK_SCHEME
en el siguiente bloque. Creo que la persona vería claramente y al momento dónde se
insertan los cambios. Todo lo que sea visualmente rápido facilita la integración.
<activity>
<intent-filter>
<data android:scheme="{YOUR_DEEPLINK_SCHEME}"/>
</intent-filter>
</activity>
Para la línea de los metadatos en los rich push, se ha explicado más adelante en un apartado dedicado. Se mencionan los rich push dentro de este apartado pero creo que no debería mencionarse ni configurarse nada hasta el propio apartado de rich push.
Notificaciones push
Cuando se muestran las líneas de código a introducir, se puede resumir o agrupar por fichero y reducir la cantidad de
bloques. También, cuando se menciona que necesita el fichero google-services.json
, y justo antes de los bloques de
código de server key legacy, convendría introducir un bloque de código con las dependencias que Firebase menciona cuando
hacemos la configuración.
import io.emma.android.model.EMMAPushOptions
import androidx.core.content.ContextCompat
class ExampleApplication : Application() {
}
A la hora de configurar las notificaciones push desde Firebase, nos muestra esta configuración:
class MainActivity : AppCompatActivity(){
EMMA.getInstance().requestNotificationsPermission();
EMMA.getInstance().requestNotificationPermission()
}
Respecto a añadir el método override fun onNewIntent(intent: Intent)
, no lo he añadido y me han funcionado bien las
notificaciones push. Si bien es cierto, que para abrir los enlaces profundos sí lo he tenido que añadir. La sintaxis del
método quedaría así:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
EMMA.getInstance().onNewNotification(this, intent, true);
EMMA.getInstance().onNewNotification(intent, true)
}
Configuración de Firebase
Tras descargarme el fichero JSON, los enlaces para probar las notificaciones PUSH no direccionan bien al epígrafe concreto que menciona cada texto. Todos llevan al mismo punto. Sobre el uso de un servicio personalizado para las notificaciones, se usa un bloque de código pero no se menciona ni explica nada más. También falta mencionar las importaciones y poner el punto antes de las notificaciones rich push.
import com.google.firebase.messaging.RemoteMessage
import io.emma.android.enums.EMMAPushType
import io.emma.android.push.EMMAPushNotificationsManager
class FirebaseMensajes : FirebaseMessagingService() {
private fun isPushFromEMMA(remoteMessage: RemoteMessage): Boolean {
return remoteMessage.data["eMMa"] == "1"
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (isPushFromEMMA(remoteMessage)) {
EMMAPushNotificationsManager.handleNotification(applicationContext, remoteMessage.data)
}
}
override fun onNewToken(token: String) {
EMMAPushNotificationsManager.refreshToken(applicationContext, token, EMMAPushType.FCM)
}
}
Finalmente, sobre el funcionamiento de los botones con acciones, no he encontrado ningún problema. A pesar de esto, sí he visto una funcionalidad del uso de botones que no se puede modificar. Cuando se usan botones en las notificaciones push, ésta desaparece, no se mantiene en la pantalla para permitir que se pulsen otros botones. Tras revisar en la configuración de mensajes push de EMMA, no he encontrado ninguna configuración al respecto. Solo he encontrado soluciones a nivel de código en el lado del cliente (IDE).
Rich push
La configuración de los push enriquecidos se puede hacer de dos formas: Mediante el SDK de EMMA (usando la clase
EMMADeepLinkActivity
para filtrar los deeplinks) o mediante una clase controladora aparte. Me ha resultado un poco
confusa la explicación de la documentación, puesto que se menciona la clase controladora auxiliar como un paso a
realizar:
Añade la actividad a ser lanzada, en forma de
<meta-data>
dentro del tag<application>
enAndroidManifest.xml
. Esta actividad será lanzada cuando el SDK ejecute un deeplink:<meta-data android:name="io.emma.DEEPLINK_OPEN_ACTIVITY" android:value="com.your.package.CustomDeeplinkActivity"/>
En cambio, siguiendo estos pasos, puede darse la situación en que también se configure el fichero AndroidManifest
con
los parámetros scheme
y host
y entonces ya no funciona la redirección de funcionamiento hacia la clase controladora
que se menciona en la cita anterior. Entonces, considero que el apartado de push enriquecidos debería estructurarse de la
siguiente forma. En las explicaciones se muestran los bloques de código corregidos de la documentación de integración.
El índice debería tener los puntos: "Configuración mediante EMMA" y "Configuración personalizada". Los pasos de configuración de cada punto deberían ser los siguientes.
Introduce la configuración de filtro de enlaces profundos en el `AndroidManifest`
En la documentación ya se ha introducido una actividad
EMMADeepLinkActivity
en el ficheroAndroidManifest
, por lo que posiblemente, ya lo tendrás y el bloque que aparece es redundante ponerlo.Asegurate de tener la actividad
EMMADeepLinkActivity
configurada de la siguiente forma, así como ajustar los permisos.AndroidManifest.xml<activity android:name="io.emma.android.activities.EMMADeepLinkActivity" android:theme="@android:style/Theme.NoDisplay" android:exported="true" android:launchMode="singleTask" android:noHistory="true" android:permission="com.example.emmaintegrationtest.PERMISSION" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="emma" /> </intent-filter> </activity>
Es posible que también tengas dentro de esa configuración el bloque
intent-filter
de los powlink, déjalo debajo delintent-filter
, de forma que tendrás dos. Como se puede ver, el esquema que vamos a usar para estos enlaces esemma
.Configura los filtros de las actividades
Ahora, dentro del fichero
AndroidManifest
hay que insertar los bloques<intent-filter>
para que se pueda reconocer los enlaces. Un ejemplo sería el siguiente:AndroidManifest.xml<activity android:name=".PowlinkActivity" android:exported="true" android:permission="com.example.emmaintegrationtest.PERMISSION"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="powlinks" android:scheme="emma" /> </intent-filter> </activity>
Ahora, cuando la aplicación recibe una notificación push y el usuario pulsa en dicha notificación, se podrá redireccionar directamente a la actividad
PowlinkActiviy
. La cual es una pantalla de ejemplo de la app.Configuración y lanzamiento de la notificación push
Ahora ya puedes lanzar la notificación push desde la plataforma. Asegurate de configurar la casilla "Rich push URL" con el valor
emma://cupones
en el segundo paso ("2. Contenido") de configuración de notificaciones push.
A continuación, para configurar las notificaciones push enriquecidas mediante una clase auxiliar, sigue los siguientes pasos.
Elimina los filtros de las actividades a mostrar
Como ahora vamos a usar una clase diferente a
EMMADeepLinkActivity
para redirecionar los enlaces, ya no necesitamos declarar los filtros en las actividades. De esta forma, elimina aquellos bloquesintent-filter
en las actividades que quieras mostrar. Se pueden quedar, dichas actividades, de la siguiente forma (como ejemplo):AndroidManifest.xml<activity android:name=".ui.notification.CouponsActivity" android:exported="true" android:permission="com.example.emmaintegrationtest.PERMISSION"> </activity>
La actividad
CouponsActivity
es una pantalla de ejemplo que se usa en mi app.Declara la clase auxiliar en el manifiesto
Como se menciona en la documentación, tienes que declarar en el manifiesto que ahora vas a usar una clase diferente para usar los enlaces. Tendrás que insertar el siguiente bloque en el fichero:
AndroidManifest.xml<manifest> <application> <meta-data android:name="io.emma.DEEPLINK_OPEN_ACTIVITY" android:value="com.example.emmaintegrationtest.ui.navigation.DeepLinkHandler" /> </application> </manifest>
Mi clase la he puesto en el package
com.example.emmaintegrationtest.ui.navigation.
y es necesario poner la ruta completa dentro deandroid:value
. Si la ruta no es correcta el SDK devolverá un log de error genérico.Cuando la ruta no está bien configurada en
android:value
, el SDK debería devolver algún mensaje relacionado. Actualmente, muestra un mensaje de error genérico y tiendes a pensar que viene de otro sitio.Creación de clase controladora
A continuación, hay que crear una clase que reciba los enlaces y redireccione al usuario a la actividad correcta. Es un concepto similar al de una API. La clase deberá ser una actividad aunque no muestre un UI como tal. Entonces, la clase controladora es la siguiente:
DeepLinkHandler.ktclass DeepLinkHandler : AppCompatActivity() { }
La clase necesitará, como se menciona en la documentación, declarar el meétodo
override fun onNewIntent(intent: Intent)
así como invocar aEMMA.getInstance().checkForRichPushUrl()
en el métodofun onCreate(savedInstanceState: Bundle?)
. Después la lógica de redirección se muestra en el métodofun handle(uri: Uri?)
yfun checkSchema(uri: Uri?)
, aunque se puede introducir también dentro defun onCreate(savedInstanceState: Bundle?)
. Por lo tanto, la clase quedaría de la siguiente forma (mostrando las líneas antiguas que se muestran en la documentación y cambiándolas por las corregidas).DeepLinkHandler.ktpackage com.example.emmaintegrationtest.ui.navigation import android.content.Intent import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.example.emmaintegrationtest.PowlinkActivity import com.example.emmaintegrationtest.ui.notification.CouponsActivity import com.example.emmaintegrationtest.ui.other.RichPushErrorActivity import io.emma.android.EMMA import io.emma.android.utils.EMMALog class DeepLinkHandler : AppCompatActivity() { private val SCHEME = "emma" private val COUPONS_ACTIVITY_HOST_VALUE = "cupones" private val POWLINKS_ACTIVITY_HOST_VALUE = "powlinks" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) EMMA.getInstance().checkForRichPush() EMMA.getInstance().checkForRichPushUrl() if (intent.data != null ) { // Show basic information about the recieved deeplink EMMALog.d("schema:${intent.data?.scheme}, host:${intent.data?.host}") // Check schema value checkSchema(intent.data) // Handle the recieved deeplink handle(intent.data) } } /** * Dispatch the activity depending of the recieved 'host' value, start the activity and finish it. */ private fun handle(uri: Uri?) { val intent = when (uri?.host) { COUPONS_ACTIVITY_HOST_VALUE -> Intent(this, CouponsActivity::class.java) POWLINKS_ACTIVITY_HOST_VALUE -> Intent(this, PowlinkActivity::class.java) else -> Intent(this, RichPushErrorActivity::class.java) } startActivity(intent) finish() } /** * Checks if scheme value inside the intent data equals to the configured scheme value. This * mentioned value is established in [SCHEME]. */ private fun checkSchema(uri: Uri?) { if (!uri?.scheme.equals(SCHEME)) { EMMALog.w("El valor 'scheme' recibido no coincide con el configurado en la app.") finish() } } /** * The deeplink inside the rich push is handled after not-null check. * {@inheritDoc} * * @see handle */ override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent); EMMA.getInstance().onNewNotification(intent, true); } }
Lanzamiento desde EMMA
Ahora, ya puedes lanzar la notificación push desde la platafora de la misma forma que en la configuración por defecto.
Registro e inicio de sesión de usuarios
No he encontrado información sobre cómo se contabilizan los registros de los usuarios en EMMA. Ni en la documentación de integración ni en la de funcionamiento de la plataforma. A la hora de hacer pruebas con la funcionalidad de registro, he encontrado que el identificador del usuario se puede modificar usando los métodos de registro e inicio de sesión. Muestro, a continuación, los pasos que he seguido:
- Creo los métodos de inicio de sesión y registro. Para identificarlos, en registro usaré mi nombre y en inicio de sesión el de mi compañero Matías. He creado dos botones, uno representa el registro y otro el inicio de sesión. Cuando se pulsan, se invoca al las funcionalidades mencionadas. Se pretende mostrar cómo, al invocar a las diferentes funcionalidades, el identificador del usuario cambia en la plataforma.
// Se omiten importaciones
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Identificadores de botones
val botonRegistro = findViewById<Button>(R.id.button_registro)
val botonLogin = findViewById<Button>(R.id.button_login)
// Acción de registro
botonRegistro.setOnClickListener {
EMMA.getInstance().registerUser("pascual", "pascual@ext.arkana.io")
}
// Acción de login
botonLogin.setOnClickListener {
EMMA.getInstance().loginUser("matias", "matias@ext.arkana.io")
}
}
}
Tras pulsar en el botón de registrar, la plataforma identifica el registro. Si voy al apartado de "Gente" en EMMA, la plataforma muestra un registro con
ID de cliente | ID de dispositivo |
---|---|
pascual | aaed(...) |
Ahora, al pulsar en el botón de inicio de sesión y usar el mismo filtro, el identificador del cliente ha cambiado. Pone
"matias" en lugar de "pascual". Se observa que no hay persistencia en el identificador. Si probamos a filtrar usando
otros parámetros, como el sistema operativo, vemos que el resultado es el mismo. Además, respecto al inicio de sesión
automático, se hace mención al uso del método EMMA.loginDefault()
pero no lo encuentro dentro de `EMMA.getInstance().
Transacciones
En la documentación se mencionan los métodos necesarios para cada tipo de transacción, en cambio, no se ofrece
documentación sobre los parámetros que usan dichos métodos. Tienes que ir usando el IDE para que te muestre los parámetros.
En cambio, creo que a veces no es suficiente para comprender el funcionamiento del método. Además, al pulsar en
"Download documentation", salta el error Task 'ijDownloadSources1942b537-ffc' not found in project ':app'
.
Al realizar una transacción no se obtiene retroalimentación inmediata que permita saber si la operación se ha ejecutado correctamente. Únicamente, podemos revisarlo en la plataforma EMMA, pero los datos tardan tiempo en reflejarse.
Perfil de usuario
A la hora de recibir el identificador de usuario, incluso implementando la interfaz EMMAUserInfoInterface
, el IDE nos
devuelve el valor kotlin.Unit
. No nos proporciona el valor del usuario. La configuración del idioma con la
línea EMMA.getInstance().setUserLanguage()
no está disponible (quizás con versiones posteriores lo estará). Finalmente,
a las sintaxis de las implementaciones les falta el paréntesis, de otra forma saltaría error. También hay algunos métodos
cuya sintaxis no es correcta o provoca errores en el IDE, por ejemplo:
import org.json.JSONObject
class MainActivity : AppCompatActivity(), EMMAUserInfoInterface {
override fun OnGetUserInfo(userInfo: JSONObject?) {
userInfo?.let { // [code --]
userInfo?.let { info: JSONObject -> // [code --]
// Do something with userInfo
}
}
override fun OnGetUserID(id: Int) {
// Not implemented
}
}
Respecto a la obtención de la información de usuario con getUserID()
, el método descrito en la documentación
devuelve kotlin.Unit
, no el identificador de usuario.
Atribución
No se he encontrado dificultad a la hora de implementar el método de atribución. Se pone un enlace para conocer los campos de atribución y poder trabajar con ellos en el código. A pesar de eso, hay poca información sobre la atribución. En la documentación de integración no se menciona nada sobre cómo probarlo.
El enlace que menciona la documentación, parece que no es accesible desde el índice. Me ha faltado algo de contexto sobre las atribuciones, lo que he encontrado al respecto es en la documentación de funcionamiento de EMMA, donde se mencionan las campañas de atribución. En cambio, no he encontrado nada sobre qué información puedo recibir al implementar el código y qué configuración es necesaria en EMMA para hacerlo funcionar (si tengo que crear una campaña de atribución, cómo tengo que crearla, una vez creada cómo la vinculo, etc.).
ProGuard
He podido importar la configuración sin problemas. No han saltado errores ni he tenido que modificar nada. A pesar de eso, sí que veo necesario algún bloque de información donde se avise cómo activar el Proguard. Como la activación del siguiente comando:
android {
buildTypes {
release {
isMinifyEnabled = true
}
}
}
O bien, un pequeño bloque de información donde se recuerden los requisitos necesarios para usarlo.
NativeAds
Comienzo con el primer bloque de código donde se muestra la clase NativeAdsActivity
. A primera vista, intuyo que todo el
código de la clase hay que copiarlo y pegarlo en nuestro proyecto y que se puede utilizar sin modificaciones. En cambio,
no ha sido posible y he tenido que realizar unas modificaciones que, más adelante, describiré.
La clase mostrada implementa varias interfaces y necesita incluir sus métodos, en cambio, no hay información de qué métodos son y para qué se utilizan. Quizás sería mejor introducir un bloque de información donde se mencionen qué métodos hacen falta, por qué y una breve explicación de los mismos. Esto se puede sustituir por un bloque de comentarios dentro del código que, al implementar, también hereden los comentarios y se pueda tener una breve explicación. Solo en aquellos métodos cuya signatura no explique su funcionamiento.
En cuanto al tipo de actividad de la clase BaseActivity
el IDE me muestra un error y he tenido que cambiarla por
AppCompatActivity
. Más tarde he tenido que eliminar ese tipo de actividad, e incluir un callback en el constructor.
Es la forma que he encontrado de hacer funcionar la clase cambiando la menor cantidad de cosas posibles. En mi clase
MainActivity`, he creado un método que infla el anuncio y recibe los datos del anuncio. Tal que así:
He creado un botón que, al pulsar, se activa todo el funcionamiento. Para poder ver el anuncio, he creado un contenedor en la vista principal. También he creado una vista para el anuncio. Lo que hace mi programa es recibir la información del anuncio dentro de la vista del anuncio. Luego, la vista del anuncio la introduce dentro del contenedor de la vista principal.
// Se omiten importaciones
class MainActivity : AppCompatActivity(), EMMAUserInfoInterface {
private lateinit var botonNativeAds : Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
botonNativeAds = findViewById(R.id.button_mostrar_nativead)
nativeAdEvent()
}
/**
* Evento que lanza el nativead a la pantalla
*/
private fun nativeAdEvent() {
botonNativeAds.setOnClickListener {
val recievedAd: (EMMANativeAd) -> Unit = extractAndInflate()
val nativeAdHandler = NativeAdHandler(recievedAd)
nativeAdHandler.getNativeAd("plantilla-nativead-android")
}
}
/**
* Infla la vista con la información del anuncio y actualiza la pantalla.
*/
private fun extractAndInflate(): (EMMANativeAd) -> Unit {
val recievedAd: (EMMANativeAd) -> Unit = { nativeAd ->
// Inflar vista
val inflater = LayoutInflater.from(this)
val adView = inflater.inflate(R.layout.activity_native_ads, null, false)
// Guardar datos del anuncio
val title = nativeAd.nativeAdContent["Title"]?.fieldValue
val body = nativeAd.nativeAdContent["Body"]?.fieldValue
// Meter datos dentro los campos
adView.findViewById<TextView>(R.id.textview_titulo).text = title
adView.findViewById<TextView>(R.id.textview_cuerpo).text = body
// Impresión a EMMA
EMMA.getInstance().sendInAppImpression(CommunicationTypes.NATIVE_AD, nativeAd)
// Meter vista en el contenedor
val container = findViewById<RelativeLayout>(R.id.nativead_container)
container.removeAllViews() // Limpiar el contenedor
container.addView(adView) // Añadir el nuevo anuncio
}
return recievedAd
}
}
package com.example.emmaintegrationtest.nativeads
import io.emma.android.EMMA
import io.emma.android.enums.CommunicationTypes
import io.emma.android.interfaces.EMMABatchNativeAdInterface
import io.emma.android.interfaces.EMMAInAppMessageInterface
import io.emma.android.interfaces.EMMANativeAdInterface
import io.emma.android.model.EMMACampaign
import io.emma.android.model.EMMANativeAd
import io.emma.android.model.EMMANativeAdRequest
class NativeAdHandler(
private val onAdReceived: (EMMANativeAd) -> Unit
) : EMMAInAppMessageInterface, EMMABatchNativeAdInterface, EMMANativeAdInterface {
override fun onReceived(nativeAd: EMMANativeAd) {
onAdReceived(nativeAd)
}
fun getNativeAd(templateId: String) {
val nativeAdRequest = EMMANativeAdRequest()
nativeAdRequest.templateId = templateId
EMMA.getInstance().getInAppMessage(nativeAdRequest, this)
}
fun getNativeAdBatch(templateId: String) {
val nativeAdRequest = EMMANativeAdRequest()
nativeAdRequest.templateId = templateId
nativeAdRequest.isBatch = true
EMMA.getInstance().getInAppMessage(nativeAdRequest, this)
}
override fun onBatchReceived(nativeAds: MutableList<EMMANativeAd>) {
nativeAds.forEach { nativeAd ->
nativeAd.tag?.let { tag ->
println("Received batch nativead with tag: $tag")
}
}
}
fun sendNativeAdClick(nativeAd: EMMANativeAd) {
EMMA.getInstance().sendInAppClick(CommunicationTypes.NATIVE_AD, nativeAd)
}
fun openNativeAd(nativeAd: EMMANativeAd) { EMMA.getInstance().openNativeAd(nativeAd) }
override fun onShown(p0: EMMACampaign?) {
// not implemented
}
override fun onHide(p0: EMMACampaign?) {
// not implemented
}
override fun onClose(p0: EMMACampaign?) {
// not implemented
}
}
Quizás quedaría más claro recordando al usuario que puede hacer una prueba para estos anuncios activando la depuración tal que:
import io.emma.android.EMMA
class MiAplicacion: Application() {
override fun onCreate() {
EMMA.getInstance().setDebuggerOutput(true)
}
}
De esta forma, dentro de Logcat, se puede ver el contenido sin necesidad de inflar la vista. Para crear la vista he
tenido que modificar la clase y no me ha funcionado la que se muestra en la documentación. Respecto al párrafo
“Obtendremos toda la información del NativeAd disponible para el usuario referente al templateId, según las condiciones
que se hayan configurado en la plataforma de EMMA.”, no me ha quedado claro qué identificador tengo que usar. Cuando se
menciona templateId, en realidad se hace alusión al identificador de la plantilla que se ha usado para crear el mensaje
NativeAd. Por esto, creo que sería más sencillo para el usuario crear un pequeño bloque de texto donde se recuerde qué
plantilla necesita el NativeAd, qué identificador necesitamos y dónde se introduce. Menciono lo de dónde se introduce
porque no se menciona explícitamente a qué método hay que invocar para hacerlo funcionar. Sí se mencionan los métodos
getNativeAd(templateId: String)
y `getNativeAdBatch(templateId: String), pero no se dice explícitamente que son esos
métodos los que hay que invocar y cómo hay que hacerlo.
El método onBatchReceived
() se activa cuando tenemos varios native ads, pero si no se incluye una etiqueta de
identificación, el método no hace nada. Quizás vendría bien incluir algo de información al respecto. También sería útil
tener información sobre el comportamiento del carrousel de native ads. Para la apertura o el seguimiento de clicks, al
método sendInAppClick()
le falta un parámetro por mencionar y explicar, que es la campaña.
StartViews, Adball, Banner y StripView
Para los Startviews, no se explica su funcionamiento. Si tengo dos campañas activas de Startviews, como en el código no indico cual quiero mostrar, se muestra la última creada. La otra sigue activa. No se puede asignar una plantilla usando los métodos. Además, para los Startviews, Adball, Banner y Strip se muestra el bloque de código pero no se indica dónde ponerlo. Tampoco se menciona que el adball y el banner no necesitan ninguna configuración adicional, con el bloque de código y la configuración en EMMA ya funcionan.
Todos los tipos de mensajes in-app no están enlazados a su explicación en la documentación de la plataforma.
Cupones
Respecto a los cupones, su integración me ha parecido confusa inicialmente. En la documentación se nos facilita una clase con una breve explicación de la misma y sus métodos, pero de primeras no he entendido cómo éstos interaccionan entre sí y cómo se sincronizan con la plataforma. Creo que sería buena idea recordar que los cupones solo muestran la información necesaria para canjear o descontar precios en el local físico, que no hay funcionamiento de compras y descuentos con los cupones. Se menciona en la documentación de la plataforma pero no se recuerda ni se enlaza ninguna explicación dentro de la documentación de integración.
Respecto al canjeo y cancelación de cupones, no se menciona cómo funciona en relación a la plataforma ni dónde se pueden observar las analíticas de los cupones dentro de EMMA.
No están enlazados los apartados de ambas documentaciones.
Plugins personalizados
Inicialmente, la explicación de los plugins personalizados puede resultar difícil. Proporcionar un esquema donde se muestren los pasos necesarios podría ayudar. No se explica qué pasos son necesarios dentro de la plataforma de EMMA para poder recibir el mensaje o, al menos, para poder hacer una prueba de funcionamiento mínimo.
- El método
getField()
está deprecated y no se proporciona alternativa para sustituirlo. Yo he usado otros métodos que sí se pueden usar como alternativa. - El valor
templateId
que se pide para usar los plugins, no es únicamente el de la plantilla. Es el valor concatenado de la etiquetaemma-plugin
y el nombre que le hayamos puesto a la plantilla.
Entonces, sintetizando las clases y métodos que necesitamos, tenemos lo siguiente:
- Clase
Application
: Dentro de esta clase necesitamos indicar qué clase de nuestra app va a gestionar el plugin personalizado. Esto se hace con la instrucciónEMMA.getInstance().addInAppPlugins(CustomPlugin())
, donde en mi casoCustomPlugin()
será mi clase gestora. Esto está bien explicado en la documentación. - Clase 'Activity': Aquí hacemos la solicitud del plugin introduciendo el identificador de la plantilla. Yo lo he puesto
dentro de un evento
setOnClickListener{ }
. Esto está bien explicado en la documentación. - Clase personalizada: Esta será la que incluya los métodos
getId()
,show()
ydismiss()
(el cual pone en la documentación que no está implementado en el SDK). Esto está bíen explicado dentro de la documentación.
Respecto a los métodos que se utilizan para trabajar con el contenido, todo el contenido del anuncio se maneja en el
método override fun show(context: Activity?, nativeAd: EMMANativeAd)
. Dentro de este, un ejemplo de uso sería el siguiente:
override fun show(context: Activity?, nativeAd: EMMANativeAd) {
context?.let {
// Mostramos todas las claves del mapa
for (key in nativeAd.nativeAdContent.keys) {
EMMALog.v("$logBanner key_value: $key")
}
// Mostramos el contenido que se ha puesto en la plataforma
for (value in nativeAd.nativeAdContent.values) {
EMMALog.v("$logBanner field_value: ${value.fieldValue}")
}
}
}
La variable logBanner
es un formato que le he dado yo para poder mostrar el nombre de la clase. No guarda relación con el
SDK. Lo he hecho así:
class CustomPlugin: EMMAInAppPlugin() {
private val logBanner = "${this.javaClass.simpleName} class | "
}
De esta forma, al pulsar en el botón que activa la solicitud, obtenemos por Logcat toda la información. Se muestra de la siguiente forma:
EMMA com.example.emmaintegrationtest V CustomPlugin class| key_value: Body
EMMA com.example.emmaintegrationtest V CustomPlugin class| field_value: prueba de plugin
Para poder lanzar el anuncio a una vista, se podría inflar (como se ha hecho en otros puntos), lo cual queda a disposición de cliente.
Respecto al plugin de ejemplo que se ofrece, creo que es una configuración demasiado compleja para ofrecerla como ejemplo. Se pueden ofrecer diferentes ejemplos donde uno sea una configuración mínima (como la que he puesto ahora) y luego, otro o varios que sean más complejos.
Creo que es necesario explicar más pasos para entender el cómo crear un plugin de forma rápida y sencilla.
Revisión de enlaces caídos o rotos
Dependencias
- Enlace del apartado: https://developer.emma.io/es/android/integracion-sdk#dependencias
- Enlace caído o roto: https://docs.emma.io/es/configuracion#general-emma-key
- Descripción: Te lleva al apartado general de la documentación
Integración Notificaciones Push
- Enlace del apartado: https://developer.emma.io/es/android/integracion-sdk#integraci%C3%B3n-notificaciones-push
- Enlace caído o roto: https://docs.emma.io/es/comunicacion/mensajes-out-app/push-notifications#habilita-fcm-android-para-las-notificaciones-push
- Descripción: Dentro del bloque rojo se menciona un artículo para obtener el Sender ID y Server Key. El enlace te lleva al apartado general de notificaciones push y no al artículo en concreto.