Application embarquée sur Android
Sur Android, votre application conseiller charge l’application embarquée dans une WebView plutôt que dans une iframe. L’application échange avec votre code Kotlin par un pont que vous injectez, avec les mêmes messages que sur le web, plus ce qu’un téléphone demande : le bouton retour du système, les liens à ouvrir hors de l’application, et un scanner que votre application lance quand la WebView ne sait pas scanner.
Le reste ne change pas : la configuration dans le tableau de bord, les clés de signature et le token conseiller que signe votre backend.
Comment ça s’articule
Section intitulée « Comment ça s’articule »Votre application Android (WebView) Dialog loadUrl ────────────────────────────────▶ application embarquée ◀── dialog:ready ──▶ dialog:auth { token } (signé par votre backend) ──▶ dialog:shopper { email } ──▶ dialog:back (bouton retour du système) ◀── dialog:back-result { handled } ◀── dialog:scan-requested (la WebView ne sait pas scanner) ──▶ dialog:open-product { productId } ◀── dialog:open-url { url } (le lien de paiement) ◀── dialog:pre-cart-sentL’application se charge au premier niveau de la WebView, pas dans une iframe : sur Android, il n’y a pas d’origine autorisée à déclarer dans le tableau de bord. C’est votre application qui réserve le pont à l’origine de Dialog.
Prérequis
Section intitulée « Prérequis »androidx.webkit1.3 ou plus récent, pourWebViewCompat.addWebMessageListener. VérifiezWebViewFeature.WEB_MESSAGE_LISTENERà l’exécution.androidx.browser, pour ouvrir le lien de paiement dans un Custom Tab.- La permission
CAMERAdans votre manifeste, demandée à l’exécution, si le conseiller scanne dans l’application.
Charger l’application
Section intitulée « Charger l’application »private const val DIALOG_ORIGIN = "https://assistant-app.askdialog.com"
webView.settings.javaScriptEnabled = truewebView.settings.domStorageEnabled = truewebView.settings.mediaPlaybackRequiresUserGesture = false
// Sans le pont, l'application ne reçoit pas son token : prévenez au lieu de la charger.if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) { showWebViewUpdateRequired() return}
// Avant loadUrl : le pont est injecté au chargement de la page.WebViewCompat.addWebMessageListener(webView, "DialogHost", setOf(DIALOG_ORIGIN)) { _, message, _, _, replyProxy -> dialogHost = replyProxy message.data?.let { onDialogMessage(JSONObject(it)) }}
webView.loadUrl("$DIALOG_ORIGIN/?apiKey=VOTRE_CLE_API_PUBLIQUE&locale=fr-FR&scan=native")Le pont doit s’appeler DialogHost, et sa règle d’origine doit être exactement l’origine de Dialog : le pont n’existe alors que dans la page de Dialog, jamais dans une page vers laquelle un lien mènerait. Les messages sont du texte JSON dans les deux sens.
| Paramètre | Obligatoire | Description |
|---|---|---|
apiKey |
oui | Votre clé API publique, comme sur le web. |
locale |
non | Une locale BCP 47 (fr-FR, en-GB). Par défaut, celle de l’appareil. |
countryCode |
non | Pays sur deux lettres, quand le marché diffère de la région de la locale. |
scan |
non | native quand votre application sait scanner les codes elle-même. Voir Scanner. |
Messages
Section intitulée « Messages »Votre application ne peut répondre qu’après le premier message de l’application : le replyProxy arrive avec dialog:ready.
private var dialogHost: JavaScriptReplyProxy? = null
private fun send(type: String, vararg fields: Pair<String, Any>) { val message = JSONObject().put("type", type) fields.forEach { (key, value) -> message.put(key, value) } dialogHost?.postMessage(message.toString())}
private fun onDialogMessage(message: JSONObject) { when (message.getString("type")) { "dialog:ready", "dialog:token-expiring" -> lifecycleScope.launch { send("dialog:auth", "token" to backend.fetchDialogToken()) } "dialog:back-result" -> if (!message.getBoolean("handled")) finish() "dialog:open-url" -> CustomTabsIntent.Builder().build() .launchUrl(this, Uri.parse(message.getString("url"))) "dialog:scan-requested" -> scanner.scan { code -> send("dialog:open-product", "productId" to code) } "dialog:pre-cart-sent" -> { // draftOrderName, invoiceUrl, invoiceSent } }}De l’application vers votre code
Section intitulée « De l’application vers votre code »| Message | Contenu | Quand |
|---|---|---|
dialog:ready |
aucun | L’application est chargée. Répondez par dialog:auth, puis dialog:shopper si vous le connaissez. |
dialog:token-expiring |
aucun | Environ 60 secondes avant l’expiration du token, ou après un refus de Dialog. Répondez par un nouveau dialog:auth. |
dialog:back-result |
{ handled } |
La réponse à dialog:back. false sur l’écran d’accueil de l’application : fermez l’écran vous-même. |
dialog:open-url |
{ url } |
Un lien que l’application ne peut pas ouvrir : une WebView le chargerait à la place de l’application. Aujourd’hui, le lien de paiement d’un pré-panier envoyé. Ouvrez-le dans un Custom Tab. |
dialog:scan-requested |
aucun | Le conseiller veut scanner et la WebView ne sait pas le faire. Scannez, puis répondez par dialog:open-product. |
dialog:pre-cart-sent |
{ draftOrderName, invoiceUrl, invoiceSent } |
Comme sur le web. |
De votre code vers l’application
Section intitulée « De votre code vers l’application »| Message | Contenu | Rôle |
|---|---|---|
dialog:auth |
{ token } |
Le token conseiller, comme sur le web. |
dialog:shopper |
{ email?, customerId? } |
Le client que sert le conseiller, comme sur le web. |
dialog:back |
aucun | Le bouton retour du système. L’application ferme son panneau ouvert ou revient d’un écran, puis répond par dialog:back-result. |
dialog:open-product |
{ productId } |
Ouvre l’assistant sur un produit : le code lu par votre scanner, ou un produit choisi par votre application. |
Le bouton retour
Section intitulée « Le bouton retour »Transmettez chaque appui sur retour à l’application, et ne quittez l’écran que quand elle répond n’avoir rien sur quoi revenir.
onBackPressedDispatcher.addCallback(this) { if (dialogHost == null) finish() else send("dialog:back")}L’application scanne les QR codes avec la caméra quand la WebView le permet. Toutes les WebView Android ne le permettent pas, selon l’appareil. Avec scan=native dans l’URL, l’application passe par votre scanner quand elle ne sait pas scanner, ou quand le conseiller a refusé la caméra : elle envoie dialog:scan-requested, et vous répondez par dialog:open-product. N’importe quel scanner convient, par exemple la lecture de codes-barres de ML Kit.
Le QR code contient l’identifiant du produit, comme sur le web. Le conseiller peut toujours saisir le code à la place.
Pour le scanner de la WebView, accordez la caméra à la page de Dialog une fois que votre application a la permission CAMERA :
webView.webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { val fromDialog = request.origin.toString().trimEnd('/') == DIALOG_ORIGIN if (fromDialog && PermissionRequest.RESOURCE_VIDEO_CAPTURE in request.resources) { request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) } else { request.deny() } }}Pour aller plus loin
Section intitulée « Pour aller plus loin »- Application embarquée : la configuration dans le tableau de bord et le token conseiller.
- Intégration du catalogue : le catalogue que parcourt l’application embarquée.
