Aller au contenu

Implémentation (pour les devs)

Annexe technique. Le reste de la section Tracking est lisible sans ça.

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 (enum AnalyticsEvent), appelé via analytics.track(event:properties:), injecté par Factory.
  • Android : kaiho-android/app/src/main/java/com/kaiho/app/data/service/AnalyticsService.kt (enum AnalyticsEvent).

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().

Identique iOS / Android :

  • captureApplicationLifecycleEvents = true → l’ouverture d’app est capturée automatiquement (event lifecycle Application Opened). Le code actuel n’émet pas d’event maison app_opened — un ancien app_opened subsiste 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 $rageclick par 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 avant identify() ne créent pas de profil.
  • preloadFeatureFlags = false, sendFeatureFlagEvent = false.
  • debug activé uniquement en build DEBUG.
  • identify() est appelé à l’inscription (nouvel utilisateur) et à la connexion (revenant, via SessionBootstrapper).
  • À 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é $email sur 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.
  • 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 (true ou false). 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 que true, 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.
  • Compteur de vues paywall : chaque paywall_viewed incrémente un compteur local utilisé pour l’éligibilité à la promo « comeback » (voir Monétisation).

  • Dédup des succès : achievement_unlocked ne part qu’une fois par appareil (set persistant), pour ne pas re-fire après une mise à jour.

  • key_completed ne part qu’une fois par clé et par appareil, via un set persisté — même mécanisme que achievement_unlocked, et pour la même raison.

  • $set de notification_permission_status n’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.