Aller au contenu
Tous les articles

Quand demander la permission de notification sur Android (et comment se remettre d'un refus)

Un guide pratique de la permission d'exécution POST_NOTIFICATIONS sur Android — quand la demander, comment amorcer la requête, et que faire quand quelqu'un la refuse.

MFKAPPS 6 min de lecture

Depuis Android 13, les notifications ne sont plus accordées par défaut — l’utilisateur doit dire oui explicitement à POST_NOTIFICATIONS, de la même façon qu’il dirait oui à l’accès à la caméra ou à la localisation. Pour une app où les notifications portent toute la valeur — un rappel d’hydratation, une alerte de médicament — cette unique boîte de dialogue système fait toute la différence entre une app qui fonctionne et une app qui n’est plus qu’une icône qu’on ouvre de temps en temps. Si le timing est raté, la plupart des gens tapent « Refuser » par réflexe, avant même d’avoir vu une raison de dire oui. Si le timing est bon, la même boîte de dialogue devient une formalité. La permission elle-même ne change pas ; c’est la demande qui l’entoure qui change.

La boîte de dialogue ne se déclenche qu’une fois

POST_NOTIFICATIONS se comporte comme toute autre permission d’exécution Android : le système affiche sa boîte de dialogue une fois par installation. Si l’utilisateur la refuse, l’appel suivant à requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) renvoie silencieusement false — pas de boîte de dialogue, pas d’erreur, juste un refus immédiat. C’est ce qui piège les gens pendant les tests : une deuxième requête ActivityResultLauncher a l’air de ne rien faire, alors qu’en réalité elle fait exactement ce qu’elle est censée faire.

val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) {
        scheduleDailyReminder()
    } else {
        showNotificationsDisabledState()
    }
}

Puisqu’il n’y a qu’une seule vraie chance d’obtenir la boîte de dialogue système, le moment où vous appelez launch() compte plus que la manière dont vous l’appelez.

Ne demandez pas au premier lancement

Le réflexe est de demander toutes les permissions dont l’app aura jamais besoin pendant l’onboarding, pour que le reste de l’app puisse supposer qu’elles sont toutes accordées. Pour les notifications en particulier, c’est presque le pire moment possible. Au premier lancement, l’utilisateur n’a aucun contexte expliquant pourquoi une app de rappel voudrait l’interrompre plus tard — il vient de voir l’icône pendant dix secondes. Une boîte de dialogue de permission sans raison préalable se lit comme une friction, et une friction à ce moment précis récolte plus souvent un « Refuser » réflexe qu’un choix réfléchi.

La solution consiste à demander au moment où la valeur de la permission devient évidente d’elle-même. Dans Hydrame, c’est le moment où quelqu’un vient de terminer de définir un objectif quotidien d’eau — il vient de dire à l’app quand il veut boire davantage, donc un rappel n’est pas une interruption, c’est la fonctionnalité qu’il a demandée. Dans OldSchool, c’est juste après l’ajout du premier médicament et de son horaire. La demande suit une action qui implique que l’utilisateur veut déjà ce que la permission permet ; la boîte de dialogue système ne fait que le formaliser.

Un écran d’amorçage vaut mieux qu’une boîte de dialogue système nue

La boîte de dialogue de permission système est laconique par conception — un titre, un nom d’app, Autoriser et Refuser. Elle ne peut pas expliquer pourquoi, et Android ne vous donne aucun moyen de personnaliser son texte. Ce que vous contrôlez, c’est l’écran affiché juste avant : une seule phrase de contexte, formulée autour de l’objectif de l’utilisateur plutôt que du besoin de l’app.

"Je te rappellerai de boire de l'eau selon le programme que tu viens de définir.
 Android a besoin de ta permission pour afficher ces rappels."

 [ Activer les rappels ]   [ Pas maintenant ]

On appelle parfois cela un écran de « pré-permission » ou de « demande douce », et sa valeur n’est pas cosmétique — il change ce que signifie la boîte de dialogue système lorsqu’elle apparaît un instant plus tard. Sans lui, la boîte de dialogue de l’OS est la première et la seule explication que reçoit l’utilisateur. Avec lui, la boîte de dialogue de l’OS n’est qu’une confirmation de quelque chose qu’il a déjà accepté. Un « Pas maintenant » sur l’écran d’amorçage ne coûte rien non plus : il ne touche pas du tout la vraie permission, donc l’unique tentative système reste disponible plus tard, une fois que l’utilisateur aura passé plus de temps dans l’app et aura plus de raisons de faire confiance à la demande.

Un refus n’est pas définitif — shouldShowRequestPermissionRationale

Une fois que quelqu’un refuse la boîte de dialogue système, ActivityCompat.shouldShowRequestPermissionRationale() vous indique s’il vaut la peine de montrer à nouveau votre propre justification avant la prochaine demande, ou si l’utilisateur l’a refusée définitivement (il a coché « Ne plus demander », ou l’a déjà refusée une fois selon les règles plus récentes et plus strictes de re-demande d’Android).

fun onReminderCardTapped(activity: Activity) {
    val granted = ContextCompat.checkSelfPermission(
        activity, Manifest.permission.POST_NOTIFICATIONS
    ) == PackageManager.PERMISSION_GRANTED

    when {
        granted -> scheduleDailyReminder()
        ActivityCompat.shouldShowRequestPermissionRationale(
            activity, Manifest.permission.POST_NOTIFICATIONS
        ) -> showRationaleThenRequest()
        else -> openAppNotificationSettings(activity)
    }
}

Quand elle renvoie false après un refus antérieur, le système ne montrera plus sa boîte de dialogue, quelle que soit la façon dont vous appelez launch() — le seul chemin de retour est la page des paramètres de notification de l’app, atteinte via un Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) ciblé sur votre package. Ne cachez pas cela derrière un message générique du type « vérifie tes paramètres » ; l’intent amène l’utilisateur directement au bon écran, sans navigation nécessaire.

Concevez le « non » comme un état réel, pas un bug

Certains utilisateurs vont refuser et le penser vraiment — construisez l’app pour que ce soit un état supporté, pas un état cassé. Hydrame continue à enregistrer l’eau sans rappels ; seuls le streak et l’historique ne reçoivent plus de coup de pouce. OldSchool garde la liste des médicaments dans l’app entièrement utilisable, et affiche une bannière persistante et masquable — « Les rappels sont désactivés » — plutôt que de prétendre que la fonctionnalité existe alors qu’elle ne peut pas se déclencher. Un échec silencieux, où un rappel a été programmé mais n’apparaît jamais parce que la permission n’a jamais été accordée, est pire qu’une fonctionnalité manquante honnête : cela donne l’impression que l’app n’est pas fiable plutôt que d’un choix fait par l’utilisateur.

L’unique boîte de dialogue système mérite qu’on s’y prépare

POST_NOTIFICATIONS n’est pas une API compliquée — registerForActivityResult et une seule chaîne de permission couvrent la mécanique en quelques lignes. Ce qui détermine réellement le taux d’acceptation, c’est tout ce qui entoure ces lignes : demander après le moment où la valeur est devenue évidente, amorcer avec une phrase de contexte en langage clair, et avoir une vraie réponse au refus plutôt que de supposer qu’il n’arrivera pas. Pour toute app où le rappel est le produit, ce n’est pas du polish — c’est ce qui fait fonctionner la fonctionnalité, tout court.