Когда запрашивать разрешение на уведомления в Android (и как восстановиться после отказа)
Практическое руководство по разрешению POST_NOTIFICATIONS, запрашиваемому во время выполнения в Android — когда его просить, как подготовить запрос и что делать, если кто-то отказал.
Начиная с Android 13 уведомления больше не выдаются по умолчанию — пользователь должен явно согласиться на POST_NOTIFICATIONS, точно так же, как он соглашается на доступ к камере или геолокации. Для приложения, где уведомления несут в себе всю ценность — напоминание пить воду, оповещение о приёме лекарства, — этот единственный системный диалог решает, будет ли приложение работать или превратится в иконку, которую иногда открывают. Ошибитесь с моментом — и большинство людей рефлекторно нажмут «Не разрешать», ещё не увидев причины согласиться. Выберите момент правильно — и тот же диалог станет обыденностью. Само разрешение не меняется; меняется запрос вокруг него.
Диалог срабатывает только один раз
POST_NOTIFICATIONS ведёт себя так же, как любое другое runtime-разрешение Android: система показывает свой диалог один раз за установку. Если пользователь отказывает, следующий вызов requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) молча возвращает false — без диалога, без ошибки, просто мгновенный отказ. Именно это сбивает людей с толку при тестировании: второй запрос ActivityResultLauncher выглядит так, будто ничего не происходит, хотя на самом деле он делает ровно то, что должен.
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
scheduleDailyReminder()
} else {
showNotificationsDisabledState()
}
}
Поскольку реальный шанс показать системный диалог только один, момент вызова launch() важнее, чем то, как именно вы его вызываете.
Не спрашивайте при первом запуске
Рефлекторное решение — запросить при онбординге все разрешения, которые приложению когда-либо понадобятся, чтобы дальше по коду можно было считать, что все они уже выданы. Для уведомлений это едва ли не худший из возможных моментов. При первом запуске у пользователя нет никакого контекста, зачем приложению-напоминалке прерывать его в будущем — он видит иконку всего десять секунд. Диалог разрешения без предшествующей причины воспринимается как трение, а трение именно в этот момент чаще получает рефлекторное «Не разрешать», чем осознанное согласие.
Решение — спрашивать в тот момент, когда ценность разрешения самоочевидна. В Hydrame это момент, когда человек только что закончил задавать дневную норму воды — он только что сказал приложению, когда хочет пить больше, поэтому напоминание — не помеха, а именно та функция, которую он запросил. В OldSchool это момент сразу после добавления первого лекарства и его расписания. Запрос следует за действием, которое подразумевает, что пользователь уже хочет того, что даёт разрешение; системный диалог лишь формализует это.
Экран подготовки лучше, чем голый системный диалог
Системный диалог разрешения намеренно лаконичен — заголовок, название приложения, «Разрешить» и «Не разрешать». Он не может объяснить почему, и Android не даёт способа настроить его текст. То, что вы контролируете, — это экран, показанный прямо перед ним: одно предложение контекста, сформулированное вокруг цели самого пользователя, а не потребности приложения.
«Я буду напоминать тебе пить воду по графику, который ты только что задал.
Android нужно твоё разрешение, чтобы показывать эти напоминания».
[ Включить напоминания ] [ Не сейчас ]
Такой экран иногда называют «пре-разрешением» или «мягким запросом», и его ценность не косметическая — он меняет то, что значит системный диалог, когда появляется мгновение спустя. Без него диалог ОС — первое и единственное объяснение, которое получает пользователь. С ним диалог ОС — лишь подтверждение того, на что пользователь уже согласился. «Не сейчас» на экране подготовки тоже ничего не стоит: он вообще не затрагивает настоящее разрешение, поэтому единственная системная попытка остаётся доступной и позже, когда пользователь проведёт в приложении больше времени и у него появится больше оснований доверять этому запросу.
Отказ — не окончательный приговор — shouldShowRequestPermissionRationale
Как только пользователь отказывает в системном диалоге, ActivityCompat.shouldShowRequestPermissionRationale() сообщает вам, стоит ли снова показать собственное обоснование перед следующим запросом, или пользователь отказал навсегда (отметил «Больше не спрашивать» либо уже отказал один раз по более новым, более строгим правилам 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)
}
}
Когда после предыдущего отказа возвращается false, система больше не покажет свой диалог, как бы вы ни вызывали launch() — единственный путь назад — страница настроек уведомлений приложения, куда ведёт Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS), привязанный к вашему пакету. Не прячьте это за общим сообщением вроде «проверьте настройки»; этот intent ведёт пользователя прямо на нужный экран, без необходимости куда-то переходить самостоятельно.
Проектируйте «нет» как реальное состояние, а не как баг
Некоторые пользователи откажут осознанно и всерьёз — постройте приложение так, чтобы это было поддерживаемым состоянием, а не сломанным. Hydrame продолжает логировать воду без напоминаний; просто серия дней и история не получают подталкивающего сигнала. OldSchool оставляет список лекарств внутри приложения полностью рабочим и показывает постоянный, закрываемый баннер — «Напоминания отключены» — вместо того чтобы делать вид, будто функция существует, когда она не может сработать. Тихий отказ, при котором напоминание было запланировано, но никогда не появилось, потому что разрешение так и не было выдано, хуже, чем честно отсутствующая функция: он выглядит как ненадёжность приложения, а не как выбор, который сделал сам пользователь.
Единственный системный диалог стоит подготовки
POST_NOTIFICATIONS — не сложный API: registerForActivityResult и одна строка с названием разрешения покрывают механику в несколько строк кода. Что на самом деле определяет процент выданных разрешений — это всё, что окружает эти строки: запрос после момента, когда ценность стала очевидной, подготовка одним предложением понятного контекста и наличие реального ответа на отказ вместо предположения, что его не случится. Для любого приложения, где напоминание и есть продукт, это не полировка — это то, благодаря чему функция вообще работает.
// По теме
Ещё из журнала
API SplashScreen в Android в 2026 году: холодный запуск без белой вспышки
Практическое руководство по API SplashScreen в Android — настройка темы, ограничения размера анимированной иконки, которые никто не читает, и keepOnScreenCondition для данных, которые ещё не готовы.
Динамический цвет Material You в Jetpack Compose: как сохранить фирменный цвет, когда побеждают обои
dynamicColorScheme() заменяет вашу палитру той, что построена из обоев пользователя. Практическое руководство по гармонизации фирменных цветов вместо их потери.
In-App Review API в Android: как просить оценку, не раздражая пользователя
Практическое руководство по In-App Review API от Google — как он на самом деле работает, когда его запускать и почему обычный попап «оцените нас» незаметно портит вашу оценку в Play Store.