Comme d’habitude, voici le résumé pour ceux qui n’ont pas le temps de toute lire 🚀

Sur IOS
  • Il existe des APIs officielles de temps d'écran: Family Controls, DeviceActivity et ManagedSettings
  • Tu bloques des apps sans jamais savoir lesquelles: les identifiants sont opaques
  • C'est le système qui bloque à ta place
  • Il te faut jusqu'à 4 extensions et un App Group pour un scénario complet
Sur Android
  • Il n'existe aucune API dédiée: tu assembles toi-même les briques
  • La seule approche fiable passe par un AccessibilityService
  • Tu vois tout: nom des apps, temps réel, historique d'usage
  • C'est toi qui comptes, qui décides et qui affiches l'écran de blocage
  • La validation Google Play est un peu plus exigeante qu'Apple sur ce sujet

Ça fait maintenant quelques mois que je travaille sur Octoscroll, ma troisième application, dont le principe est simple:

Tu définis un temps limite quotidien sur les apps de ton choix, et une fois ce temps écoulé, l’app est bloquée. Pour la débloquer, il faut répondre correctement à un quiz de culture générale.

Le concept tient en deux phrases. L’implémentation, elle, m’a occupée bien plus longtemps que prévu 😅.

Parce que “bloquer une app”, c’est exactement le genre de fonctionnalité qui a l’air triviale vue de l’extérieur, et qui se révèle être un des trucs les plus contre-intuitifs que j’ai eu à développer. Sur les deux plateformes, mais pour des raisons complètement différentes.

Et comme j’ai galéré à trouver du contenu là-dessus, surtout en français, je me suis dit que ça méritait un article.

1. Le problème de base: une app ne peut pas en bloquer une autre 🚧

Commençons par le commencement, parce que c’est important de comprendre pourquoi c’est compliqué.

Sur mobile, chaque application tourne dans son propre bac à sable (son sandbox). Elle ne voit pas les autres apps, ne peut pas lire leurs données, ne peut pas les contrôler. C’est volontaire et c’est une très bonne chose: imagine si n’importe quelle app téléchargée pouvait en fermer une autre ou lire ce qui s’y passe… 😬

Sauf que voilà: une app de bien-être numérique a besoin de faire exactement ça. Savoir quelle app tu utilises, combien de temps, et t’empêcher de continuer.

Les deux OS ont donc fini par entrouvrir une porte. Mais pas la même porte, pas avec la même philosophie, et surtout pas avec le même niveau de confort pour le développeur.

Apple a construit une API dédiée, très encadrée, où c’est le système qui fait le travail à ta place.

Google, lui, n’a jamais construit d’API pour ça. À la place, les développeurs détournent une API prévue pour tout autre chose: l’accessibilité.

Autant te dire que l’expérience de développement n’est pas du tout la même des deux côtés 🙃

2. IOS: Family Controls, l'API qui bloque les yeux fermés 🍎

Sur IOS, tout tourne autour de trois frameworks qui fonctionnent ensemble:

  • FamilyControls — demande l'autorisation à l'utilisateur, et lui fait choisir les apps à surveiller
  • DeviceActivity — surveille le temps d'usage et prévient quand un seuil est atteint
  • ManagedSettings — pose/retir le blocage, qu'Apple appelle le shield (le "bouclier")

C’est ce qu’on appelle collectivement l’API Screen Time, sortie avec IOS 15.

L'entitlement: la bonne surprise

Premier point de blocage potentiel: ces frameworks ne sont pas accessibles librement. Il te faut un entitlement spécial, com.apple.developer.family-controls, et pour distribuer ton app sur l’App Store avec, il faut en faire la demande à Apple.

Alors je vais être honnête: j’appréhendais un peu cette étape, parce que j’avais lu des retours de gens qui parlaient de refus, d’attente interminable, de justifications à rallonge, etc.

Dans mon cas, c’est passé tout seul: un formulaire à remplir, moins de 10 minutes, aucun aller-retour, aucune question supplémentaire. Je crois que si ton cas d’usage est légitime et clairement expliqué, ça se passe bien.

C’était de loin la partie la plus facile du projet 😄

Le concept clé: le token opaque

Et maintenant, le truc qu’il faut vraiment comprendre avant d’écrire la moindre ligne de code, parce qu’il conditionne toute l’architecture.

Quand l’utilisateur choisit les apps à surveiller, il le fait via un sélecteur fourni par Apple, le FamilyActivityPicker. Tu affiches ce sélecteur, l’utilisateur coche ses apps, et tu récupères… des tokens opaques (des ApplicationToken).

Un token, c’est une boîte noire. Tu peux le stocker, le comparer, le passer à l’API pour dire “bloque celle-là”. Mais tu ne peux pas:

  • savoir de quelle app il s'agit (pas de nom, pas de bundle id);
  • afficher son icône ou son nom dans ta propre interface;
  • lire son temps d'usage depuis ton app;
  • la relancer une fois le blocage levé.

Autrement dit: tu bloques des apps sans jamais savoir lesquelles.

C’est brillant côté vie privée, il faut le reconnaître. Apple a conçu l’API pour qu’un développeur malveillant ne puisse rien apprendre sur toi. Rend-toi bien compte que même une app de contrôle parental ne sait pas ce que tu utilises.

Mais côté produit, ça a des conséquences très concrètes.

Dans Octoscroll, quand tu réussis ton quiz sur Android, je peux te proposer un bouton “Retourner sur Instagram” qui relance l’app directement. Sur IOS, c’est tout simplement im-po-ssible. Tu dois ressortir et rouvrir l’app toi-même.

Ce n’est pas un manque de soin de ma part, c’est une limite infranchissable de la plateforme. Et ça vaut la peine de le savoir avant de dessiner tes plans, plutôt que de découvrir en fin de développement qu’un tiers de ton parcours utilisateur n’est pas réalisable 🙃

3. IOS: l'architecture réelle, ou pourquoi une app ne suffit pas 🏗️

Voilà le point où, personnellement, j’ai décroché la première fois que j’ai lu la documentation. Allez, courage, on attaque.

Pour un scénario complet comme le mien, ton app n’est plus juste une app. C’est une app + plusieurs extensions, qui tournent chacune dans leur propre processus, avec leurs propres droits, et qui ne partagent strictement rien par défaut.

Dans Octoscroll, j’en ai quatre:

  • Le moniteur (DeviceActivityMonitor ) — c'est lui qu'IOS réveille quand une app atteint sa limite du jour, ou au changement de journée. C'est le seul endroit où je peux réagir "en direct" à un dépassement, même quand Octoscroll est fermée.
  • La configuration du bouclier (ShieldConfigurationDataSource ) — l'écran que tu vois quand tu ouvres une app bloquée.
  • L'action du bouclier (ShieldActionDelegate ) — ce qui se passe quand tu appuies sur le bouton de cet écran.
  • Le rapport d'activité (DeviceActivityReport ) — une vue en lecture seule capable d'afficher le temps d'usage réel.

Et comme ces processus sont isolés, la seule façon de partager de l’information entre eux, c’est un App Group: un espace de stockage commun, déclaré dans les entitlements de chaque cible, dans lequel tout le monde lit et écrit.

Dans mon cas, tout l’état du blocage vit là-dedans: les limites par app, les tokens, les flags “cette app est bloquée aujourd’hui”, les minutes bonus gagnées au quiz, etc.

Le détail qui coûte une semaine

Et maintenant, la contrainte qui m’a le plus surprise, et qui explique une bonne partie des choix bizarres de mon code.

Ton app hôte, celle que l’utilisateur ouvre, ne peut pas lire le temps d’écran. Pas du tout. Elle sait qu’elle a demandé un seuil de 15 minutes, elle sait si le moniteur lui a signalé un dépassement, mais elle est incapable de répondre à la question “combien de minutes ont été consommées aujourd’hui ?”.

La seule chose capable de lire cet usage, c’est l’extension de rapport.

SAUF qu’elle est en lecture seule: ce qu’elle affiche, elle ne peut pas te le transmettre. Ses écritures dans l’App Group sont ignorées par le système.

Si t’as bien suivi, en ce moment, tu dois être comme moi y a quelques mois, c’est-à-dire: 🥵🥵🥵😅😅😅🤯🤯🤯

Parce que concrètement, ça veut dire que sur l’écran d’accueil d’Octoscroll, la petite zone qui affiche “il te reste X minutes sur Instagram” n’est pas du Flutter. C’est une vue native SwiftUI, rendue par l’extension, que j’insère dans mon interface Flutter comme un îlot autonome. Elle affiche des données que le reste de mon application ne verra jamais.

Et c’est de là que vient le seul vrai talon d’Achille de l’approche IOS: si l’état que tu maintiens diverge de la réalité que seul le système connaît, tu n’as aucun moyen de comparer les deux. Tu es aveugle.

C’est exactement le contexte des pièges de la section suivante.

4. IOS: les pièges qui m'ont coûté le plus de temps ⏳

Je ne vais pas rentrer dans le détail du code, ce serait indigeste et très spécifique à mon implémentation. Mais je veux te donner les quatre pièges dans lesquels je suis tombée, parce qu’ils ne sont documentés nulle part et que tu tomberas très probablement dans les mêmes si tu as un projet similaire.

Piège n°1: IOS a de la mémoire

Quand tu demandes à surveiller un seuil, tu lui donnes un nom. Et IOS mémorise “ce seuil a été atteint” en l’associant à ce nom.

Le problème: si tu réutilises le même nom le lendemain, IOS considère que le seuil a déjà été atteint et ne te préviendra plus jamais.

Résultat: ton blocage fonctionne parfaitement le jour 1… et ne se déclenche plus du tout à partir du jour 2. Et évidemment, ça ne se voit pas en développement, où tu relances ton app cinquante fois par jour.

La parade: générer un nom unique à chaque fois que tu (ré)armes ta surveillance.

Piège n°2: le bouclier fantôme

Corollaire direct du premier, et beaucoup plus vicieux.

Quand tu ré-armes ta surveillance en cours de journée (parce que l’utilisateur change une limite, parce qu’il a gagné du temps au quiz, ou simplement parce qu’il a rouvert l’app), IOS peut te re-livrer les seuils déjà atteints plus tôt dans la journée.

Traduction côté utilisateur: il vient de gagner 5 minutes en répondant à son quiz, et l’app se re-bloque instantanément. Alors qu’il est largement sous sa limite.

C’est ce que j’appelle dans mon code le “bouclier fantôme”: l’îlot natif affiche fièrement “8 minutes restantes”, mais l’app est bloquée quand même. Frustration garantie.

La parade: marquer chaque armement d’un identifiant, et ignorer toute notification qui porte l’identifiant d’un armement précédent.

Piège n°3: minuit n'est pas fiable

L’API te propose un événement “début d’intervalle” pour remettre tes compteurs à zéro chaque jour. Sur le papier, parfait.

En pratique: si le téléphone est éteint, si l’utilisateur n’a pas touché son téléphone, ou si le système a décidé que ton extension pouvait attendre, cet événement ne part pas. Et les blocages de la veille survivent tranquillement à la nouvelle journée.

La parade: ne jamais faire confiance à minuit. Je stocke la date du dernier reset et je vérifie à chaque ouverture de l’app si la journée a changé. Ceinture ET bretelles.

Piège n°4: l'option qui sauve tout

Heureusement, il y a une bonne nouvelle. Depuis IOS 17.4, tu peux demander à ce qu’un seuil prenne en compte l’usage déjà écoulé dans la journée au moment où tu l’armes.

Ça a l’air anodin, mais c’est absolument central. Parce que ça veut dire qu’à tout moment, tu peux jeter ton propre état, tout ré-armer, et laisser IOS recalculer quelle app doit être bloquée à partir de l’usage réel.

C’est ce qui m’a permis d’ajouter un bouton “recalculer les boucliers” dans l’app: la seule porte de sortie quand mon état et la réalité du système ont divergé. Vu que je ne peux pas lire le temps d’écran, je ne peux pas vérifier qui a raison. Mais je peux tout jeter et laisser le système redécider.

Le vrai enseignement

Si je devais résumer ces quatre pièges en une phrase:

👉🏻 sur IOS, tu maintiens en permanence un état parallèle à celui du système, sans jamais pouvoir le comparer à la vérité.

5. Android: pas d'API, deux briques à assembler 🤖

Changement complet d’ambiance.

Sur Android, il n’existe tout simplement pas d’équivalent à Family Controls. Google propose bien “Bien-être numérique”, mais c’est une app système, pas une API que tu peux utiliser.

Donc tu dois construire toi-même, à partir de deux besoins:

  • Savoir quelle app est au premier plan, en temps réel;
  • Dessiner par-dessus quand tu décides de bloquer.

Le deuxième point est le plus simple: c’est la permission SYSTEM_ALERT_WINDOW, celle qui permet d’afficher une fenêtre par-dessus les autres apps (le principe des bulles de chat Messenger). Tu construis ta vue, tu la poses en plein écran, et voilà ton bouclier.

Le premier point, en revanche, c’est là que tout se joue. Et il y a trois approches possibles:

  • UsageStatsManager — l'API officielle de statistiques d'usage. Fiable pour l'historique, mais ce n'est pas du temps réel: tu dois l'interroger en boucle, et tu apprends toujours les choses avec un peu de retard. Vraiment pas pratique si tu veux bloquer une app à la minute près.
  • Un service en premier plan qui interroge cette API en continu — c'est le grand classique des tutos.
  • Un service d'accessibilité, qui reçoit une notification du système à chaque changement de fenêtre.

J’ai commencé par la deuxième. J’ai fini par la troisième. Voilà pourquoi.

6. Android: pourquoi j'ai abandonné le service en premier plan 💀

Ma première version tournait avec un foreground service qui interrogeait UsageStatsManager toutes les secondes. Ça marchait. Sur mon téléphone de test, c’était même impeccable.

Et puis il y a eu le 6 juillet.

Un utilisateur sous Samsung, et un comportement totalement incompréhensible: une journée entière sans aucun décompte, aucun blocage, aucune erreur, aucun crash. Comme si l’app Octoscroll n’existait simplement pas.

Explication: Samsung, comme la plupart des constructeurs asiatiques (Xiaomi, Huawei, OnePlus, et j’en passe), applique ses propres optimisations de batterie par-dessus celles d’Android. Et ces optimisations tuent silencieusement les services en arrière-plan qu’elles jugent trop gourmands. Sans prévenir l’utilisateur, et sans prévenir l’app, ça serait trop facile sinon 😅

Ton service tourne parfaitement pendant deux heures, puis il meurt. Et personne n’est au courant.

Alors tu peux essayer de te battre: demander l’exemption d’optimisation batterie, redémarrer le service, ajouter un receveur au démarrage… C’est ce que font beaucoup d’apps, et c’est un combat qu’on ne gagne jamais vraiment, avec des règles différentes pour chaque constructeur.

La solution que j’ai retenue, c’est celle qu’utilisent les applications de référence du secteur, comme Opal ou StayFree: le service d’accessibilité.

L’idée est un peu détournée, mais imparable. Un service d’accessibilité, c’est prévu pour les lecteurs d’écran et les aides à la navigation. Comme c’est une brique d’assistance à l’utilisateur, le système le traite avec un statut à part: il le lie lui-même, le relance automatiquement, y compris après un redémarrage du téléphone, et les optimisations de batterie ne l’assassinent pas.

Et surtout, il change complètement le modèle: au lieu d’interroger le système toutes les secondes pour savoir ce qui se passe, c’est le système qui te prévient à chaque changement de fenêtre. Plus de polling, plus de batterie brûlée pour rien.

Le passage à cette architecture a résolu le problème Samsung, et a supprimé toute une catégorie de bugs que je passais mon temps à colmater.

Une précision importante sur la vie privée

Je préfère être très claire là-dessus, parce que “service d’accessibilité” fait légitimement peur, et à raison: c’est une API puissante, capable de lire le contenu de ton écran. C’est d’ailleurs le vecteur d’attaque favori de beaucoup de malwares Android.

Sauf que cette capacité de lecture se déclare. Dans la configuration de mon service, l’option de lecture du contenu de l’écran est explicitement désactivée, et je ne m’abonne qu’à un seul type d’événement: “une nouvelle fenêtre passe au premier plan”.

Ce que je reçois, c’est un nom de package. com.instagram.android. Rien d’autre. Pas de texte, pas de champ de saisie, pas de frappe clavier.

C’est un point technique, mais c’est aussi un point de confiance, et on va voir dans la section validation que Google est très à cheval dessus.

7. Android: le diable est dans les détails 😈

Bon, on a notre détection et notre overlay. Sur le papier c’est fini. En pratique, voici la liste de tout ce que j’ai dû gérer en plus, et que je n’avais pas anticipé.

L'événement "changement de fenêtre" est très bavard

Naïvement, tu te dis: je reçois un package, si c’est une app surveillée je compte, sinon j’arrête. Sauf que ce n’est pas du tout ce qui se passe. Tu reçois des événements en permanence, et beaucoup ne correspondent pas du tout à un changement d’app:

  • Le volet de notifications — l'utilisateur tire la barre du haut, le système passe au premier plan une fraction de seconde, mais l'app en dessous n'a pas été quittée. Il faut ignorer et continuer à compter.
  • Le clavier — pareil, ton clavier est une app, il déclenche des événements en plein milieu d'une session. Sans filtre, tu clos la session à chaque fois que l'utilisateur écrit un commentaire.
  • Ta propre app — celle-là est subtile. Quand ton écran de blocage s'affiche, c'est ton app qui passe au premier plan. Si tu ignores tes propres événements, le décompte de l'app quittée continue à tourner. Il faut donc clore la session, sans jamais en démarrer une pour toi-même.

Le téléphone dans la poche

Celui-là, je l’ai eu en vrai, et il est très embêtant.

L’utilisateur est sur Instagram, il verrouille son téléphone et le met dans sa poche. Aucun changement de fenêtre ne se produit: pour ton service, Instagram est toujours au premier plan. Et ton compteur continue tranquillement à tourner pendant deux heures.

Il faut donc écouter l’extinction de l’écran et figer la session à ce moment-là.

Et son jumeau, le déverrouillage

Symétrique, et encore plus vicieux. L’utilisateur déverrouille son téléphone, et retombe directement dans Instagram, exactement là où il s’était arrêté.

Aucun changement de fenêtre exploitable n’est émis. Résultat: du temps d’usage totalement gratuit, jusqu’à ce que l’utilisateur change d’app.

La parade: écouter le déverrouillage, et à ce moment-là seulement, faire une requête à UsageStatsManager pour redemander au système quelle app est réellement au premier plan. On retrouve le polling, mais une fois, au bon moment, plutôt que toutes les secondes.

Minuit, encore

Même problème que sur IOS: à 00:00, personne ne touche forcément son téléphone. Sans rien, les compteurs et le blocage de la veille survivent jusqu’au premier événement du matin.

Il faut donc un petit battement régulier, chez moi toutes les minutes, dont le seul rôle est de vérifier si on a changé de jour.

Deux détails de finition

Deux derniers points, plus anecdotiques, mais qui font la différence à l’usage:

  • La vidéo continue de tourner. Tu bloques YouTube, l'utilisateur revient sur ton app pour son quiz... et le son continue en fond, en lecture Picture-in-Picture. Il faut explicitement couper les processus en arrière-plan de l'app bloquée.
  • Relancer l'app après le quiz. Depuis Android 11, tu ne "vois" plus les autres apps installées sans les déclarer explicitement dans ton manifeste. Sans cette déclaration, ton bouton "Retourner sur Instagram" ne fait simplement rien.

8. Le parcours de permissions, ou comment perdre ses utilisateurs 🚪

Voilà un aspect purement produit, mais qui découle directement des choix techniques, et qui est probablement le plus gros écart entre les deux plateformes.

Sur IOS: une demande d’autorisation. Une pop-up système, l’utilisateur accepte, c’est réglé. Ensuite le sélecteur d’apps, qui est également fourni par Apple.

Sur Android, il te faut quatre autorisations:

  • l'accès aux statistiques d'usage;
  • l'autorisation d'affichage par-dessus les autres apps;
  • l'activation du service d'accessibilité;
  • les notifications.

Et le pire, ce n’est pas leur nombre. C’est qu’aucune des trois premières ne peut être accordée par une simple pop-up. Pour chacune, tu dois envoyer l’utilisateur dans les réglages système, dans un écran différent, où il doit trouver ton app dans une liste et basculer un interrupteur. Puis revenir.

Et pour l’accessibilité, l’écran système affiche en prime un avertissement assez inquiétant sur le fait que l’app pourra “observer vos actions”.

Autant dire que c’est un massacre en termes de conversion. C’est l’endroit où tu perds des utilisateurs.

Je n’ai pas de solution miracle à proposer, juste ce que j’ai fait: expliquer clairement avant chaque redirection pourquoi la permission est nécessaire et ce qu’on en fait, ne jamais enchaîner deux réglages sans un écran de transition, et vérifier au retour que ça a bien été accordé (parce que non, tu ne reçois aucun retour du système).

Si tu vises les deux plateformes, prévois simplement que ton onboarding Android sera bien plus long que ton onboarding IOS. Ce ne sont pas les mêmes contraintes, ce ne doit donc pas être le même parcours.

9. Côté Flutter: un seul canal, deux mondes qui n'ont rien en commun 🎯

Petit mot sur l’organisation du code, parce que c’est un cas intéressant.

Sur ce projet, il n’existe aucun package pub.dev que j’aie voulu utiliser. J’ai regardé, mais les enjeux étaient trop spécifiques, l’API Screen Time bouge, et je n’avais pas envie de dépendre d’un package abandonné pour la fonctionnalité centrale de mon app. Donc tout est écrit à la main: du Swift, du Kotlin, et un MethodChannel unique entre les deux.

Et c’est là que j’ai dû accepter un truc qui m’a fait mal au début: mon interface commune n’est pas commune.

Normalement, quand tu fais un pont natif, tu essaies d’exposer la même API des deux côtés. Ici, c’est impossible. “Combien de minutes ont été consommées aujourd’hui ?” a une réponse sur Android et n’en a aucune sur IOS. “Relance cette app” fonctionne sur Android et est un non-sens sur IOS.

J’ai passé un temps ridicule à chercher comment unifier ça élégamment, avant d’abandonner et d’assumer l’asymétrie. Chaque méthode de mon service Dart documente en commentaire ce qu’elle fait sur chaque plateforme, et renvoie une valeur de repli neutre (une liste vide, false) là où la plateforme ne sait pas répondre. Y compris quand la méthode n’existe carrément pas côté natif: Flutter lève alors une exception spécifique, que je traite comme un cas normal et pas comme une erreur.

C’est moins joli qu’une belle abstraction symétrique. Mais ça reflète honnêtement la réalité des deux plateformes, et surtout, ça évite de faire croire à mon interface Flutter qu’une information est disponible alors qu’elle ne le sera jamais.

10. La validation stores: la partie que personne n'anticipe 📋

Et on arrive à mon plus gros contre-pied.

Je pensais qu’Apple serait le mur, et Google la formalité. Ça a été exactement l’inverse.

Apple: 10 minutes

Comme dit plus haut: un formulaire de demande d’entitlement, une explication du cas d’usage, envoyé. Accepté directement, sans aller-retour. La revue de l’app elle-même s’est passée normalement, sans question particulière sur le blocage.

L’explication, je pense, c’est qu’Apple a construit l’API pour ce cas d’usage. Tu utilises l’outil prévu, de la manière prévue. Il n’y a pas de débat à avoir.

Google: bien plus exigeant

Côté Play Store, la logique est inverse. Tu utilises une API d’accessibilité pour faire autre chose que de l’accessibilité. Google le sait, l’autorise, mais l’encadre sérieusement. Et honnêtement, c’est plutôt sain, vu ce que des apps malveillantes pourraient en faire.

Ce qu’il faut fournir:

  • une déclaration dans la Play Console expliquant l'usage que tu fais de l'API;
  • une divulgation dans l'app (une prominent disclosure): un écran dédié, avant d'envoyer l'utilisateur dans les réglages, qui explique ce que le service fait et ne fait pas;
  • une vidéo de démonstration du parcours;
  • une description publique de cet usage dans la fiche de l'app.

Je me suis fait rejeter le 4 août sur le dernier point: j’avais fait la déclaration, l’écran de divulgation et la vidéo, mais pas la description publique dans la fiche. Le motif affiché était assez vague, et j’ai mis un moment à comprendre ce qui manquait exactement.

Du coup, mes trois enseignements, pour t’éviter les allers-retours:

  • La description doit être dans la fiche publique, dans chaque langue où ton app est publiée. Et elle doit dire la même chose que ton écran de divulgation in-app, que le validateur verra aussi.
  • Attention au réglage de ta vidéo YouTube. Si elle est en "privé" plutôt qu'en "non répertorié", le validateur ne voit rien du tout, et tu te fais rejeter avec un motif qui ne mentionne pas la vidéo.
  • Ne coche jamais l'option qui déclare ton app comme outil d'accessibilité. Elle est réservée aux apps dont la fonction principale sert directement les personnes en situation de handicap. Une app de bien-être numérique n'en est pas une, et cocher cette case à tort est un motif de rejet à part entière.

11. En conclusion: ce que je retiens

Si tu envisages de développer ce genre de fonctionnalité, voilà ce que j’aurais aimé qu’on me dise avant de commencer.

Ce n’est pas une fonctionnalité, c’est un projet. Sur les deux plateformes, tu vas écrire du code natif, beeeeeaucoup de code natif, et penser en termes de processus séparés, d’événements système et de cas limites. Si tu comptais rester en Dart, oublie tout de suite.

IOS te rend aveugle mais fiable. Le blocage est fait par le système, il est solide, il survit à tout. Mais tu ne sais pas quelles apps tu bloques, tu ne peux pas lire le temps d’usage, et tu passes ton temps à maintenir un état parallèle que tu ne peux jamais confronter à la réalité. La plupart de mes bugs vennaient de là, et c’est honnêtement pas évident de débugger une boîte noire.

Android te donne tout mais te fait combattre les constructeurs. Tu vois les noms des apps, l’usage réel, l’historique. En échange, c’est toi qui comptes, toi qui décides, toi qui affiches, et tu dois survivre aux optimisations batterie de chaque fabricant. Le service d’accessibilité est la seule réponse que j’ai trouvée à ce problème.

Anticipe les stores dès le début. Surtout côté Google: la déclaration, l’écran de divulgation et la vidéo ne sont pas des formalités de dernière minute. Et comme ton écran de divulgation doit être cohérent avec ta fiche publique, c’est une contrainte qui remonte jusqu’à ton onboarding.

Et teste sur de vrais téléphones. Pas un. Plusieurs, de plusieurs marques. Mon bug le plus coûteux ne s’est jamais reproduit sur mon appareil de test, ni sur un émulateur. C’était un Samsung, chez un utilisateur, dans la vraie vie.

Voilà. C’est de loin la fonctionnalité la plus difficile que j’aie eu à développer sur mes trois applications, et aussi une des plus intéressantes. J’espère que ce retour t’évitera quelques arrachages de cheveux 😄

Si tu veux voir le résultat, Octoscroll est disponible sur les deux stores, et je lui ai évidemment consacré une page dédiée sur ce blog.

Et si tu bosses sur le même genre de sujet, écris-moi ! C’est un domaine où on est peu nombreux et où on galère tous sur les mêmes choses.

À la prochaine 👋

Commentaires