Notifications Android : récurrence, badges et gamification
Les notifications dans un gestionnaire de tâches, c'est critique. Si le rappel n'arrive pas au bon moment, l'app a failli. Et sur Android, faire arriver un rappel au bon moment est un combat permanent contre le système d'exploitation lui-même.
Ce n'est pas un problème de code. C'est un problème d'écosystème. Samsung, Xiaomi, Huawei, Oppo : chaque fabricant implémente sa propre politique d'économie de batterie qui peut tuer ton app et ses notifications planifiées sans prévenir. Ajoutez les badges d'icône, les push FCM et les notifications de gamification, et vous obtenez un système qui doit être à la fois fiable, cross-manufacturer et émotionnellement satisfaisant.
Points clés
- Les rappels récurrents planifient les 30 prochaines occurrences d'un coup pour survivre au kill d'Android, au lieu de planifier uniquement la prochaine occurrence.
- Chaque ouverture de l'app re-vérifie et replanifie les notifications perdues lors du redémarrage ou du kill par le système.
- Les badges d'icône sur Android nécessitent des implémentations spécifiques par launcher (Samsung, Nova, AOSP), sans API unifiée.
- Les notifications de gamification transforment un mécanisme système en moment de motivation grâce à des messages personnalisés et des icônes custom.
Pourquoi planifier 30 occurrences au lieu d'une seule ?
La réponse tient en un mot : survie. Sur Android, le système d'exploitation peut décider de tuer ton app à tout moment pour libérer de la mémoire. Quand l'app est tuée, la logique JavaScript qui devait planifier la prochaine occurrence meurt avec elle.
La stratégie naïve, c'est de planifier uniquement le prochain rappel et de replanifier le suivant quand le premier se déclenche. Problème : si l'app est tuée entre le déclenchement du rappel et la replanification, la chaîne est brisée. L'utilisateur ne recevra plus jamais de rappel.
Ma solution : planifier les 30 prochaines occurrences dès la création du rappel récurrent. Si tu as un rappel quotidien à 9h, l'app crée immédiatement 30 alarmes : une pour demain 9h, une pour après-demain 9h, et ainsi de suite. Même si l'app est tuée pendant 3 semaines, les rappels continueront d'arriver.
La documentation Android sur AlarmManager recommande d'utiliser setExactAndAllowWhileIdle pour les alarmes critiques. C'est ce que j'utilise pour chaque occurrence. Le "AllowWhileIdle" est crucial : sans ça, le mode Doze d'Android (activé quand le téléphone est immobile et non branché) peut reporter l'alarme indéfiniment.
Pourquoi 30 et pas 365 ? Parce que trop d'alarmes simultanées peut impacter les performances et la batterie. 30 jours est le compromis idéal : l'utilisateur est couvert pour un mois, et l'app rafraîchit les occurrences à chaque ouverture. J'avais déjà exploré ce type de compromis performance/fiabilité dans l'article sur la récurrence des rappels.
Comment gérer les notifications perdues au redémarrage ?
Quand un téléphone redémarre, toutes les alarmes planifiées via AlarmManager sont perdues. C'est le comportement standard d'Android. La solution officielle : écouter le broadcast BOOT_COMPLETED pour replanifier les alarmes au démarrage.
En théorie, c'est propre. En pratique, c'est un champ de mines.
Samsung bloque BOOT_COMPLETED par défaut sur certains modèles avec leur "Optimisation de la batterie". Xiaomi a MIUI qui empêche le démarrage automatique des apps sauf si l'utilisateur l'autorise manuellement dans les paramètres. Huawei avec EMUI fait la même chose. Oppo avec ColorOS, pareil. Selon le site Don't Kill My App, qui répertorie les comportements par fabricant, certains appareils bloquent jusqu'à 90% des broadcasts.
Ma solution pragmatique : ne pas compter uniquement sur BOOT_COMPLETED. À chaque ouverture de l'app, le NotificationService exécute un audit complet :
- Récupération des rappels actifs depuis la base de données locale.
- Vérification : pour chaque rappel, l'alarme correspondante existe-t-elle encore dans
AlarmManager? - Replanification des alarmes manquantes.
- Nettoyage des alarmes obsolètes (tâches supprimées, rappels désactivés).
Ce processus prend environ 200ms pour 50 rappels actifs. L'utilisateur ne le remarque pas. Mais ça garantit que même si le téléphone a redémarré, même si le fabricant a bloqué le boot broadcast, les notifications seront replanifiées dès la prochaine ouverture de l'app.
Comment Firebase Cloud Messaging gère-t-il les push de groupe ?
Les notifications locales (rappels, récurrence) sont gérées par AlarmManager sur l'appareil. Les notifications push (activité de groupe, badges débloqués, mentions) passent par Firebase Cloud Messaging.
Le flux est le suivant :
- Un utilisateur crée une tâche dans un groupe et l'assigne à un membre.
- Le backend détecte l'assignation et récupère le token FCM de l'assigné depuis la base de données.
- Le backend envoie un message FCM avec le titre, le corps et un payload de données (type d'action, ID de la tâche, ID du groupe).
- Le téléphone de l'assigné reçoit le push, même si l'app est fermée.
- Si l'utilisateur tape sur la notification, l'app s'ouvre directement sur la tâche en question grâce au deep link dans le payload.
Le piège principal avec FCM : les tokens changent. Un token FCM peut devenir invalide quand l'utilisateur désinstalle et réinstalle l'app, quand il efface les données de l'app, ou quand Google décide de le recycler. Ma stratégie : rafraîchir le token à chaque lancement de l'app et le comparer à celui stocké en DB. Si différent, mise à jour. Et côté backend, chaque erreur d'envoi FCM avec le code messaging/registration-token-not-registered déclenche un nettoyage automatique du token invalide.
C'est le même principe de résilience que j'applique dans le système de sécurité et rate limiting : anticiper les échecs, pas seulement les gérer quand ils arrivent.
Pourquoi les badges d'icône sont-ils si difficiles sur Android ?
Le petit cercle rouge avec un nombre sur l'icône de l'app. Sur iOS, c'est une ligne de code : UIApplication.shared.applicationIconBadgeNumber = 5. C'est une API système standardisée depuis iOS 3.
Sur Android, il n'existe pas d'API standardisée pour les badges d'icône. Chaque launcher implémente sa propre méthode :
- Samsung (OneUI) : utilise les Samsung BadgeProvider via un ContentProvider spécifique.
- Nova Launcher : lit les badges depuis un broadcast Intent custom.
- AOSP/Pixel : utilise le canal de notification avec
setNumber()sur la notification elle-même (pas de badge standalone). - Xiaomi (MIUI) : a sa propre API via un ContentProvider différent de Samsung.
- Huawei : utilise les Huawei Mobile Services au lieu des Google Play Services.
La bibliothèque react-native-app-badge abstrait une partie de cette fragmentation, mais ne couvre pas tous les cas. J'ai accepté cette réalité : sur certains appareils, les badges fonctionneront parfaitement. Sur d'autres, ils ne s'afficheront pas. L'app ne doit jamais dépendre exclusivement des badges pour communiquer une information importante, ils sont un bonus visuel, pas un canal critique.
Comment les notifications de gamification transforment-elles l'expérience ?
Les notifications les plus satisfaisantes ne sont pas les rappels. Ce sont les notifications de gamification. Un badge débloqué, un streak milestone atteint, un level up : ce sont des moments de célébration que l'utilisateur n'attendait pas.
J'ai mis en place trois types de notifications de gamification :
Badge débloqué
Notification locale avec une icône custom correspondant au badge. Le texte est personnalisé : "Badge Organisateur Pro débloqué ! Tu as créé 100 tâches." C'est plus engageant que "Nouveau badge débloqué". La personnalisation du message augmente le taux de rétention de la notification selon les recherches de OneSignal sur la personnalisation.
Streak milestone
"Streak de 30 jours ! Continue comme ça." Le streak est un compteur de jours consécutifs d'utilisation de l'app. Le système reconnaît des jalons : 7 jours, 30 jours, 100 jours, 365 jours. Chaque jalon déclenche une notification et des points bonus. J'avais détaillé tout le système de gamification dans l'article sur le schéma de gamification.
Level up
"Niveau 5 atteint ! Tu es maintenant Stratège." Les 12 niveaux ont chacun un nom et un seuil de points. La notification de level up est la plus rare et la plus gratifiante. C'est le même mécanisme de récompense variable que décrit B.F. Skinner dans ses recherches sur le renforcement : les récompenses imprévisibles créent un engagement plus fort que les récompenses prévisibles.
Le point commun entre ces trois types : ils transforment une notification (mécanisme souvent perçu comme intrusif) en moment positif. L'utilisateur ne subit pas la notification, il l'attend. C'est exactement la philosophie du feed de gamification : rendre l'engagement visible et gratifiant.
Comment optimiser la fiabilité des notifications sur tous les appareils ?
Si je devais résumer en une phrase : ne fais confiance à rien. Ne fais pas confiance à BOOT_COMPLETED. Ne fais pas confiance à AlarmManager en mode Doze. Ne fais pas confiance aux tokens FCM. Ne fais pas confiance aux badges d'icône.
La stratégie de défense en profondeur :
- Couche 1 : alarmes locales via
setExactAndAllowWhileIdle(30 occurrences pré-planifiées). - Couche 2 : audit et replanification à chaque ouverture de l'app.
- Couche 3 : push FCM en backup pour les rappels critiques (envoyés par le backend si l'alarme locale n'a pas été confirmée).
- Couche 4 : guidance utilisateur vers les paramètres de batterie du fabricant (écran dédié dans les settings de l'app).
Ce dernier point est souvent négligé. L'app inclut un écran qui détecte le fabricant du téléphone et guide l'utilisateur pas à pas pour désactiver l'optimisation de batterie pour TAMSIV. C'est de l'UX technique : expliquer à l'utilisateur pourquoi ses rappels pourraient ne pas fonctionner et comment y remédier. C'est la même transparence que dans l'onboarding lazy registration : être honnête sur les limitations plutôt que de les cacher.
Questions fréquentes
Les rappels récurrents fonctionnent-ils quand le téléphone est éteint ?
Non. Aucune alarme ne peut se déclencher quand le téléphone est éteint. En revanche, dès que le téléphone redémarre, l'app replanifie automatiquement les occurrences manquées à la prochaine ouverture. Les rappels passés ne sont pas envoyés rétroactivement.
Pourquoi les notifications sont-elles parfois en retard sur certains téléphones ?
C'est lié au mode Doze d'Android et aux optimisations de batterie des fabricants. TAMSIV utilise setExactAndAllowWhileIdle pour minimiser les retards, mais certains fabricants (notamment Xiaomi et Huawei) imposent des restrictions supplémentaires. La section "Notifications" des paramètres de l'app explique comment les désactiver.
Les notifications de gamification peuvent-elles être désactivées ?
Oui. Les paramètres de l'app permettent de désactiver indépendamment les notifications de badges, de streaks et de level up. Les rappels de tâches et les push de groupe sont sur des canaux séparés et ne sont pas affectés.
Le streak est-il perdu si je manque un jour ?
Le streak repasse à zéro si tu manques un jour. Cependant, le système de "streak freeze" te permet de protéger ton streak : si tu as accumulé suffisamment de points, tu peux activer un gel qui préserve ton streak pendant une journée d'inactivité.
Les badges d'icône montrent-ils le nombre exact de notifications non lues ?
Sur Samsung et les launchers compatibles, oui, le badge affiche le nombre exact. Sur les launchers AOSP standards, le badge est un simple point (présence/absence) sans nombre. C'est une limitation Android que Google n'a pas encore unifiée.