Implémentation (pour les devs)
Annexe technique. Le reste de la section Tracking est lisible sans ça.
Où vivent les events
Section intitulée « Où vivent les events »Chaque plateforme a un service central avec un enum typé des events — on ne passe jamais une chaîne libre :
- iOS :
kaiho-ios/Kaiho/Core/Data/Services/AnalyticsService.swift(enumAnalyticsEvent), appelé viaanalytics.track(event:properties:), injecté par Factory. - Android :
kaiho-android/app/src/main/java/com/kaiho/app/data/service/AnalyticsService.kt(enumAnalyticsEvent).
Ajouter un event = l’ajouter à l’enum des deux côtés (sauf event volontairement propre à une plateforme, comme l’ATT iOS), puis l’appeler depuis le ViewModel concerné. Et mettre à jour le catalogue dans la foulée.
- Tracker dans les ViewModels, jamais dans les vues « bêtes » (dumb views). Tous les events actuels partent d’un ViewModel.
- Jamais de tracking dans
onAppear/LaunchedEffect— on tracke au moment de l’action utilisateur, pas au rendu d’un écran (sinon on compte des affichages parasites). - Pas de PII dans les events. L’email est explicitement retiré des propriétés avant envoi, y compris dans
identify().
Configuration PostHog
Section intitulée « Configuration PostHog »Identique iOS / Android :
captureApplicationLifecycleEvents = true→ l’ouverture d’app est capturée automatiquement (event lifecycleApplication Opened). Le code actuel n’émet pas d’event maisonapp_opened— un ancienapp_openedsubsiste seulement dans les données des vieilles versions (voir le catalogue).captureScreenViews = false→ pas de vue d’écran automatique ; on tracke manuellement ce qui compte.- iOS seulement :
rageClickConfig.enabled = false. Le SDK iOS capture$rageclickpar défaut ; on l’a éteint le 5/8/26 (jamais lu, et le SDK Android n’a pas d’équivalent, donc pas comparable entre plateformes). personProfiles = identifiedOnly→ seuls les utilisateurs identifiés deviennent des « persons ». Les events anonymes avantidentify()ne créent pas de profil.preloadFeatureFlags = false,sendFeatureFlagEvent = false.debugactivé uniquement en build DEBUG.
Identité
Section intitulée « Identité »identify()est appelé à l’inscription (nouvel utilisateur) et à la connexion (revenant, viaSessionBootstrapper).- À l’inscription, RevenueCat
logIn()relie l’ID anonyme RevenueCat à l’ID réel, et pose l’attribut$posthogUserId(cohérence cross-plateforme). - L’app pose aussi l’attribut réservé
$emailsur le client RevenueCat, pour que le support puisse retrouver un abonné à partir de l’adresse qu’il donne (sans ça, seul l’ID utilisateur permet de chercher). Il est effacé (valeur nulle) dès que l’utilisateur passe en opt-out analytics, et reposé s’il revient en opt-in. - L’attribution TikTok a son propre
identify(), séparé de PostHog.
Confidentialité & opt-out
Section intitulée « Confidentialité & opt-out »- Un réglage de profil (« Partager les statistiques d’usage ») bascule
optOut()/optIn(): il coupe à la fois l’analytics (PostHog) et l’attribution publicitaire (TikTok). - Le mode démo désactive tout le tracking (service no-op).
- L’état d’opt-out est persistant sur l’appareil et ré-appliqué à chaque lancement.
- Remonté en base : le choix est écrit dans
users.analytics_opted_out(colonne synchronisée via PowerSync), au moment du toggle (trueoufalse). Les anciens utilisateurs — dont l’opt-out n’existait qu’en local — sont backfillés au démarrage de session : la valeur locale est poussée une fois, sans jamais écraser un opt-out déjà en base (on ne pousse quetrue, pour ne pas réactiver depuis un 2ᵉ appareil resté à l’opt-in par défaut). - Règle emailing : toute audience email doit exclure
analytics_opted_out = true— on n’écrit pas aux gens qui ont refusé le tracking. Le honoring de l’opt-out analytics lui-même reste local à l’appareil ; la remontée en base sert uniquement l’exclusion des envois.
Comportements particuliers à connaître
Section intitulée « Comportements particuliers à connaître »-
Compteur de vues paywall : chaque
paywall_viewedincrémente un compteur local utilisé pour l’éligibilité à la promo « comeback » (voir Monétisation). -
Dédup des succès :
achievement_unlockedne part qu’une fois par appareil (set persistant), pour ne pas re-fire après une mise à jour. -
key_completedne part qu’une fois par clé et par appareil, via un set persisté — même mécanisme queachievement_unlocked, et pour la même raison. -
$setdenotification_permission_statusn’est poussé que si la valeur a changé depuis le dernier envoi. Il partait à chaque passage au premier plan, pour une valeur qui ne bouge quasi jamais.