Capacitor
Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
Site officiel : https://capacitorjs.com
Journaux liées à cette note :
Journal du jeudi 09 janvier 2025 à 13:13
Nouvelle #iteration sur le Projet 17 - Créer un POC de création d'une app smartphone avec Capacitor.
Je viens de push le commit feat(android): implemented webview and configured deeplinks
. J'ai passé en tout, 11 heures sur cette itération.
Je souhaite, dans cette note de type DevLog, présenter les difficultés et les erreurs rencontrées dans cette itération.
Étape 1 : mise en place d'un dummy website totalement statique
The Capacitor application in this POC displays the content of a demonstration website, with the HTML content located in the
./dummy-website/
folder.
This website is served by an HTTP Nginx server, launched usingdocker-compose.yml
.
Pour faire très simple, j'ai choisi de créer un faux site totalement statique, qui sera affiché dans une webview de l'application smartphone.
Ce site contient juste 2 pages HTML ; celui-ci est exposé par un serveur HTTP nginx, lancé via un docker-compose.yml
.
Étape 2 : Expose dummy website on Internet
En première étape, j'ai dû mettre en place une méthode pour facilement exposer sur Internet un dummy website lancé localement :
Expose dummy website on Internet
Why?
The Android and iOS emulators do not have direct and easy access to the HTTP service (dummy website) exposed on http://localhost:8080.To overcome this issue, I use "cloudflared tunnel". You can also use other solutions, such as sish or ngrok Developer Preview. For more information, you can refer to the following note (in French): 2025-01-06_2105
Comme expliqué ci-dessus, cette contrainte est nécessaire afin de permettre à l'émulateur Android et à l'émulateur iOS (lancé sur une instance Scaleway Apple Silicon) aussi bien que sur mon smartphone physique personnel, d'accéder aux dummy website avec un support https.
Ceci était d'autant plus nécessaire, pour remplir les contraintes de configuration de la fonctionnalité Deep Linking with Universal and App Links.
C'est pour cela que j'ai dernièrement publié les notes suivantes : 2024-12-28_1621, 2024-12-28_1710, 2024-12-31_1853 et Alternatives managées à ngrok Developer Preview.
Pour simplifier la configuration de ce projet (poc-capacitor
), j'ai décidé d'utiliser "cloudflared tunnel" en mode non connecté.
J'ai installé cloudflared avec Mise (voir la configuration ici).
Pour rendre plus pratique le lancement et l'arrêt du tunnel cloudflare, j'ai implémenté deux scripts :
Voici ce que cela donne à l'usage :
$ ./scripts/start-cloudflare-http-tunnel.sh Starting the tunnel... …wait… …wait… …wait… Tunnel started successfully: https://moral-clause-interesting-broadway.trycloudflare.com
To stop the tunnel, you can execute:
$ ./scripts/stop-cloudflare-http-tunnel.sh Stopping the tunnel (PID: 673143)... Tunnel stopped successfully.
Étape 3 : configuration de la webview Capacitor
En réalité, par erreur, j'ai configuré la webview Capacitor après l'implémentation de la partie App links.
Au départ, je pensais qu'un simple window.location.href = process.env.START_URL;
était suffisant pour afficher le site web dans l'application. En réalité, cette commande a pour effet d'ouvrir la page HTML dans le browser par défaut du smartphone. Je ne m'en étais pas tout de suite rendu compte.
Dans Capacitor, pour créer une webview dans une application, il est nécessaire l'utiliser la fonction InAppBrowser.openInWebView(...
du package @capacitor/inappbrowser
.
Voici l'implémentation dans le fichier /src/js/online-webview.js
:
window.Capacitor.Plugins.InAppBrowser.openInWebView({ url: startUrl, options: { // See https://github.com/ionic-team/capacitor-os-inappbrowser/blob/e5bee40e9b942da0d4dad872892f5e7007d87e75/src/defaults.ts#L33 // Constant values are in https://github.com/ionic-team/capacitor-os-inappbrowser/blob/e5bee40e9b942da0d4dad872892f5e7007d87e75/src/definitions.ts showToolbar: false, showURL: false, clearCache: true, clearSessionCache: true, mediaPlaybackRequiresUserAction: false, // closeButtonText: 'Close', // toolbarPosition: 'TOP', // ToolbarPosition.TOP // showNavigationButtons: true, // leftToRight: false, customWebViewUserAgent: 'capacitor webview', android: { showTitle: false, hideToolbarOnScroll: false, viewStyle: 'BOTTOM_SHEET', // AndroidViewStyle.BOTTOM_SHEET startAnimation: 'FADE_IN', // AndroidAnimation.FADE_IN exitAnimation: 'FADE_OUT', // AndroidAnimation.FADE_OUT allowZoom: false }, iOS: { closeButtonText: 'DONE', // DismissStyle.DONE viewStyle: 'FULL_SCREEN', // iOSViewStyle.FULL_SCREEN animationEffect: 'COVER_VERTICAL', // iOSAnimation.COVER_VERTICAL enableBarsCollapsing: true, enableReadersMode: false } } });
Les paramètres dans options
permettent de configurer la webview. J'ai choisi de désactiver un maximum de fonctionnalités.
En implémentant cette partie, j'ai rencontré trois difficultés :
- Avec la version
1.0.2
du packages, j'ai rencontré ce bug "Bug- Android App crashing after adding this plugin, j'ai perdu presque 1 heure avant de le découvrir, pour fixer cela, j'ai choisi le QuickWin d'installer la version1.0.1
. - J'ai mis un peu de temps pour trouver les paramètres passés dans
options
- J'ai trouvé
allowZoom: false
pour supprimer l'affichage des boutons de zoom dans la webview
Étape 4 : setup de la partie App Links
Après avoir lu la page "Deep Linking with Universal and App Links", c'était la partie que je trouvais la plus difficile, mais en pratique, ce n'est pas très compliqué.
J'ai passé 5h30 sur cette partie, mais j'ai fait plusieurs erreurs.
Cette configuration se passe en 3 étapes.
Génération du fichier .well-known/assetlinks.json
La première consiste à générer le fichier le fichier dummy-website/.well-known/assetlinks.json
qui est exposé par le serveur HTTP du dummy website.
Cette opération est documentée dans la partie "Create Site Association File".
Son contenu ressemble à ceci :
[
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "$PACKAGE_NAME",
"sha256_cert_fingerprints": ["$SHA256_FINGERPRINT"]
}
}
]
Il a une fonction de sécurité, il permet d'éviter de créer des applications malveillantes qui s'ouvriraient automatiquement sur des URLs sans lien avec l'application.
Il permet de dire « l'URL de ce site web peut automatiquement ouvrir l'application $PACKAGE_NAME
» qui est signée avec la clé publique $SHA256_FINGERPRINT
.
J'ai implémenté le script /scripts/generate-dev-assetlinks.sh
qui permet automatiquement de générer ce fichier.
Lorsque j'ai travaillé sur cette partie, j'ai fait l'erreur de générer un certificat (voir le script /scripts/generate-dev-assetlinks.sh
). Or, ce n'est pas la bonne méthode en mode développement.
Par défaut, Android met à disposition un certificat de développement dans ${HOME}/.android/debug.keystore
.
La commande suivante me permet d'extraire la clé publique :
SHA256_FINGERPRINT=$(keytool -list -v \ -keystore "${HOME}/.android/debug.keystore" \ -alias "androiddebugkey" \ # password par défaut -storepass "android" 2>/dev/null | grep "SHA256:" | awk '{print $2}')
Configuration de AndroidManifest.xml
Comme indiqué ici, voici les lignes que j'ai ajoutées dans /android/app/src/main/AndroidManifest.xml.tmpl
:
<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:scheme="https" /> <data android:host="{{ .Env.ALLOW_NAVIGATION }}" /> </intent-filter>
Petite digression sur mon usage des templates dans ce projet.
J'utilise gomplate pour générer des fichiers dynamiquement à partir de 4 templates (.tmpl
) et des variables d'environnement configurées entre autres dans .envrc
.
La génération des fichiers se trouve ici :
gomplate -f capacitor.config.json.tmpl -o capacitor.config.json gomplate -f android/app/build.gradle.tmpl -o android/app/build.gradle gomplate -f android/app/src/main/AndroidManifest.xml.tmpl -o android/app/src/main/AndroidManifest.xml gomplate -f android/app/src/main/strings.xml.tmpl -o android/app/src/main/res/values/strings.xml
Les principaux éléments dynamiques sont :
export APP_NAME=myapp
export PACKAGE_NAME="xyz.sklein.myapp"
export START_URL=$(cat .cloudflared_tunnel_url)
export ALLOW_NAVIGATION=$(echo "$START_URL" | sed -E 's#https://([^/]+).*#\1#')
START_URL
contient l'URL publique générée par cloudflared tunnel qui change à chaque lancement du tunnel.
Support deep links via l'interception de l'événement appUrlOpen
Troisième étape de la configuration de App links.
let timeoutId = setTimeout(() => { openInWebView(process.env.START_URL); }, 200); window.Capacitor.Plugins.App.addListener('appUrlOpen', (event) => { clearTimeout(timeoutId); openInWebView(event.url); });
Cela permet d'implémenter la fonction deep links. Exemple : si l'utilisateur du smartphone clique sur l'URL https://dummysite/deep/ alors l'application va directement s'ouvrir sur la page /deep/
du dummy website.
Commandes utiles
La commande suivante permet de demander à l'OS Android de lancer une nouvelle "vérification" du fichier dummy-website/.well-known/assetlinks.json
:
$ adb shell pm verify-app-links --re-verify ${PACKAGE_NAME}
Note : le fichier .cloudflared_tunnel_url
contient l'URL du tunnel cloudflare qui expose le dummy website.
La commande suivante permet d'afficher la configuration actuelle App Link d'une application :
$ adb shell pm get-app-links ${PACKAGE_NAME} xyz.sklein.myapp: ID: 100ba7e3-b978-49ac-926c-8e6ec6810f5c Signatures: [AF:AE:25:7F:ED:98:49:A3:E0:23:B3:BE:92:08:84:A5:82:D1:80:AA:E0:A4:A3:D3:A0:E2:18:D6:70:05:67:ED] Domain verification state: association-pending-belt-acute.trycloudflare.com: verified
La commande suivante permet de tester le lancement de l'application à partir d'une URL passée en paramètre :
$ adb shell am start -W -a android.intent.action.VIEW -d "$(cat .cloudflared_tunnel_url)" Starting: Intent { act=android.intent.action.VIEW dat=https://sc-lo-welsh-injury.trycloudflare.com/... } Status: ok LaunchState: COLD Activity: xyz.sklein.myapp/.MainActivity TotalTime: 1258 WaitTime: 1266 Complete
Cela fonctionne aussi avec une sous-page, par exemple : "$(cat .cloudflared_tunnel_url)/deep/?query=foobar"
.
Dans l'émulateur, Chrome ne lance pas les App Link !
Je pense que ce piège m'a fait perdre 2 h (sur les 5 h passées sur cette implémentation) !
Si j'ouvre l'URL du dummy website dans Chrome, l'application n'est pas lancée.
Mais, si j'ouvre l'URL dans l'application qui se nomme "Google", celle accessible via la barre de recherche en bas ce ce screenshot, l'App Link est bien pris en compte.
Problème : je testais mon application seulement dans Chrome. Et la fonctionnalité App Links ne fonctionnait pas. C'est seulement quand j'ai installé l'application sur mon smartphone physique personnel que j'ai constaté que App Links fonctionnait sous "Firefox Android".
J'ai constaté aussi que sur mon smartphone, Chrome n'ouvrait aucune application sur les URLs youtube.com
, reddit.com
, github.com
…
D'après ce que je pense avoir compris, la liste des applications qui peuvent ouvrir les App Links est listée dans la section "Settings => Apps => Default apps" :
J'ai fait des expériences sur 3 différents smartphones Android d'amis et à ce jour, je n'ai pas encore compris comment cela fonctionne. J'ai l'impression que c'est lié au browser par défaut configuré, mais j'ai trouvé des exceptions.
En tout cas, ce piège m'a fait perdre beaucoup de temps !
Note finale
Pour le moment, je n'ai pas eu besoin de configurer @capacitor/app-launcher
, mais je pense que cela sera utile pour permettre à l'application d'ouvrir d'autres applications à partir d'une URL.
J'ai scripté pratiquement toutes les actions de ce projet.
ChatGPT m'a bien servi tout au long de cette implémentation.
Journal du vendredi 27 décembre 2024 à 11:23
#iteration Projet 17 - Créer un POC de création d'une app smartphone avec Capacitor.
Note de type #mémento à propos de la configuration de l'icône et du splash screen d'une application Capacitor.
J'utilise le plugin @capacitor/splash-screen
.
Ce plugin offre de nombreuses options de configuration : https://capacitorjs.com/docs/apis/splash-screen#configuration.
Les paramètres de configuration du splash screen sont définis ici dans mon POC poc-capacitor
.
Cela m'a pris du temps pour trouver comment modifier l'icône et le splash screen de l'application.
Cette opération est documentée sur la page suivante : "Splash Screens and Icons".
Dans poc-capacitor
, j'ai documenté cette opération ici.
La commande npx capacitor-assets generate
prend en entrée mon fichier logo ./assets/logo.png
et génère automatiquement de nombreux fichiers assets dans les dossiers suivants :
./android/app/src/main/res/
./ios/App/App/Assets.xcassets/
./src/assets/
Pour plus d'informations au sujet de cette commande, je vous invite à consulter : https://github.com/ionic-team/capacitor-assets.
Journal du jeudi 21 novembre 2024 à 17:36
Dans la note 2024-11-20_1102, je disais :
Prochaine étape du Projet 17 : Setup les iOS Requirements de Capacitor sur ce serveur Apple Silicon.
C'est chose faite 🙂.
Le repository poc-capacitor
contient maintenant un script ./scripts/deploy-ios-requirements.sh
qui permet d'exécuter ce script de provisioning sur le serveur Scaleway Apple Silicon distant : /provisioning/_ios.sh
.
Ensuite, j'ai détaillé les étapes pour :
- Uploader le projet sur le Scaleway Apple Silicon distant
- Démarrer l'émulation d'un iPhone 15
- Compiler et lancer l'application Capacitor dans l'émulateur iPhone 15
- Et visualiser l'émulateur via VNC
Tout cela est détaillé ici : https://github.com/stephane-klein/poc-capacitor/tree/f109fb23dc612f486fad0d55ba939b4679841d06?tab=readme-ov-file#launch-application-on-ios
Journal du mardi 19 novembre 2024 à 23:50
#iteration Projet 17 - Créer un POC de création d'une app smartphone avec Capacitor.
Dans la note 2024-11-19_1029, je disais :
Pour utiliser Capacitor, j'ai besoin d'installer certains éléments.
In order to develop Android applications using Capacitor, you will need two additional dependencies:
- Android Studio
- An Android SDK installation
Je me demande si Android Studio est optionnel ou non.
La réponse est non, Android Studio n'est pas nécessaire, ni pour compiler l'application, ni pour la lancer dans un émulateur Android. Android SDK est suffisant.
J'ai utilisé le plugin Mise https://github.com/Syquel/mise-android-sdk pour installer les "Android Requirements" de Capacitor. Les instructions détaillées pour Fedora sont listées dans le README.md du repository : https://github.com/stephane-klein/poc-capacitor/tree/4238e80f84a248fdb9e5bb86c10bea8b9f0fdade.
Journal du mardi 19 novembre 2024 à 10:29
#iteration Projet 17 - Créer un POC de création d'une app smartphone avec Capacitor.
Pour utiliser Capacitor, j'ai besoin d'installer certains éléments.
In order to develop Android applications using Capacitor, you will need two additional dependencies:
- Android Studio
- An Android SDK installation
Je me demande si Android Studio est optionnel ou non.
J'aimerais installer ces deux services avec Mise.
J'ai trouvé des Asdf plugins pour Android SDK :
- https://github.com/Syquel/mise-android-sdk (créé le 2024-03-03)
- https://github.com/huffduff/asdf-android-sdk (créé le 2024-10-10)
- https://github.com/arcticShadow/asdf-android (créé le 2024-11-13)
#JeMeDemande quel plugin utiliser, quelles sont leurs différences.
Pour essayer d'avoir une réponse, j'ai posté les issues suivantes :
- What are the differences with other existing plugins?
- What are the differences with other existing plugins?
Alexandre m'a informé qu'il a utilisé avec succès le plugin https://github.com/Syquel/mise-android-sdk/, il a même créé une issue https://github.com/Syquel/mise-android-sdk/issues/10 qui a été traité 🙂.
La suite : 2024-11-19_1102.
Journal du lundi 18 novembre 2024 à 21:23
J'apprends que l'app iOS LogSeq est contruit avec Capacitor.
It is worth noting that Apple accepts Capacitor-based apps (e.g., LogSeq). Capacitor renders applications in a webview, making it a viable alternative. This approach can be suggested in the documentation as a way to circumvent potential issues with publishing PWAs.
-- from
Journal du mardi 15 octobre 2024 à 16:02
En étudiant l'annonce de Brief.me j'ai un peu étudié Capacitor :
Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️ .
Le projet Capacitor a commencé fin 2017 et d'après ce que j'ai lu, ce projet est la "suite" d' Apache Cordova anciennement nommé PhoneGap.
Je me souviens d'avoir utilisé PhoneGap vers 2010.
Je découvre que je peux utiliser Svelte avec Capacitor : Svelte & Capacitor - Build native mobile apps with web technology and Svelte.
Il est même possible d'utiliser SvelteKit en mode SSG (@sveltejs/adapter-static
) : https://ionic.io/blog/cross-platform-sveltekit-capacitor-application-yes-its-possible.
J'ai parcouru ce retour d'expérience : How I published a gratitude journaling app for iOS and Android using SvelteKit and Capacitor.
#JaiDécouvert ce template svelte-capacitor.
#JaiDécouvert whatpwacando.today.
#JaiLu SvelteKit + Capacitor Performance Example
J'ai souvent entendu parler d'Ionic par le passé, mais je n'avais jamais pris le temps de m'y pencher sérieusement. Je pensais que Ionic était un équivalent de React Native, mais j'avais tort. En réalité, Ionic est un UI Toolkit.