shouldShowRequestPermissionRationale — це метод Android API, який підказує розробнику, чи потрібно показати користувачеві пояснення перед запитом небезпечного дозволу. Згідно з Android Developer Reference, 2024, метод повертає true, якщо користувач раніше відхилив запит, але не встановив прапорець Never Ask Again. Це ключовий інструмент для побудови ввічливого UX під час роботи з дозволами часу виконання.
Головне
shouldShowRequestPermissionRationale — це метод класів Activity та Fragment в Android, доступний через ActivityCompat для сумісності. Він приймає назву дозволу та повертає Boolean, який вказує, чи потрібно показати користувачеві додаткове пояснення перед повторним запитом. Метод з'явився в Android 6.0 Marshmallow разом із моделлю дозволів часу виконання.
Механізм обґрунтування побудований на відстеженні історії взаємодії користувача з діалогами дозволів. Система запам'ятовує, чи відхилив користувач запит раніше. Якщо відхилення відбулося без встановлення прапорця Never Ask Again, shouldShowRequestPermissionRationale повертає true. Це сигнал розробнику: користувач не розуміє, навіщо потрібен дозвіл, і потрібне додаткове пояснення. Згідно з Рекомендаціями Material Design від Google, показ діалогу обґрунтування після першої відмови збільшує ймовірність повторного надання дозволу на 35 відсотків.
Важливо розуміти семантику значень, що повертаються: true означає, що діалог має сенс показати, false — що діалог або не потрібен (дозвіл уже надано або ніколи не запитувався), або марний (Never Ask Again активний). Метод не є гарантією, що діалог буде показано — він лише дає рекомендацію. Розробник сам вирішує, який UI показати у відповідь.
shouldShowRequestPermissionRationale був представлений на рівні API 23 разом із групою методів для дозволів часу виконання. До Android 6.0 всі дозволи запитувалися під час встановлення, і механізм пояснення не був потрібен — користувач приймав або відхиляв весь список цілком. Модель часу виконання зробила можливою ситуацію, коли користувач відхиляє запит, не розуміючи контексту, і саме для цього потрібне обґрунтування.
Логіка методу виглядає наступним чином. При першому виклику requestPermissions для конкретного дозволу shouldShowRequestPermissionRationale повертає false — користувач ще не стикався з діалогом. Якщо користувач відхилив запит (натиснув Відхилити), метод починає повертати true. Після повторного відхилення з прапорцем Never Ask Again метод повертає false.
Повна таблиця станів:
| Стан | shouldShowRationale | checkSelfPermission | Дія розробника |
|---|---|---|---|
| Не запитувався | false | DENIED | Показати системний діалог |
| Надано | false | GRANTED | Виконати функцію |
| Відхилено вперше | true | DENIED | Показати обґрунтування, потім системний діалог |
| Never Ask Again | false | DENIED | Перенаправити в Налаштування |
Комбінація shouldShowRequestPermissionRationale = false та checkSelfPermission = DENIED — це найскладніший для обробки випадок. Він означає, що або дозвіл ніколи не запитувався, або встановлено Never Ask Again. Розробнику потрібно розрізнити ці два стани. Єдиний спосіб — зберігати прапорець isFirstRequest у SharedPreferences або використовувати SavedStateHandle. При першому запиті встановлюйте прапорець, і якщо shouldShowRationale повернув false, а прапорець уже true — це означає Never Ask Again.
shouldShowRequestPermissionRationale скидається, якщо користувач видаляє та перевстановлює додаток, очищає дані додатка або скидає налаштування дозволів. Після перевстановлення метод знову поверне false для першого запиту. Системні оновлення та зміна версії Android не скидають історію — вона зберігається в даних додатка.
Правильна реалізація обґрунтування включає три компоненти: перевірку shouldShowRequestPermissionRationale після відмови, відображення кастомного діалогу з поясненням і повторний виклик requestPermissions після позитивної відповіді користувача. Діалог має бути коротким, конкретним і пояснювати, навіщо додатку потрібен саме цей дозвіл.
private fun requestLocationWithRationale() {
val permission = Manifest.permission.ACCESS_FINE_LOCATION
when {
ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED -> {
startLocationTracking()
}
ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
) -> {
showRationaleDialog(permission)
}
else -> {
requestPermissionLauncher.launch(permission)
}
}
}
private fun showRationaleDialog(
permission: String
) {
AlertDialog.Builder(this)
.setTitle("Навіщо потрібен доступ до геолокації")
.setMessage(
"Додаток використовує геолокацію для позначення" +
" місць на карті. Без цього дозволу" +
" функція не працюватиме."
)
.setPositiveButton("Дозволити") { _, _ ->
requestPermissionLauncher.launch(permission)
}
.setNegativeButton("Скасувати", null)
.show()
}
Найкращі практики Material Design рекомендують використовувати нижній аркуш або вбудований банер замість модального діалогу для обґрунтування. Нижній аркуш менш нав'язливий і дає користувачеві контекст. Вбудований елемент на екрані (наприклад, картка з поясненням і кнопкою Дозволити) показує, що функція недоступна без дозволу, але не блокує решту інтерфейсу.
Текст обґрунтування має бути локалізований і адаптований під конкретну функцію. Не використовуйте загальні фрази на кшталт «Це потрібно для роботи додатку». Вкажіть конкретно: «Для відображення погоди поруч із вами» або «Для збереження фото в галерею». Конкретні пояснення підвищують ймовірність надання дозволу на 50 відсотків згідно з дослідженнями UX від Google.
Різниця між shouldShowRequestPermissionRationale = true (перша відмова) та false при DENIED (Never Ask Again) — ключовий момент в обробці дозволів. У першому випадку користувач вагався, і додаткове пояснення може переконати його надати доступ. У другому — користувач прийняв остаточне рішення, і повторний системний діалог лише викличе роздратування.
Алгоритм обробки після відмови має виглядати так:
Важливо не плутати порядок: спочатку перевіряти shouldShowRequestPermissionRationale, а не checkSelfPermission. checkSelfPermission все одно поверне DENIED в обох випадках. Тільки shouldShowRequestPermissionRationale відрізняє першу відмову від Never Ask Again. Використовуйте SavedStateHandle або SharedPreferences для зберігання прапорця «перший запит був» — це єдиний надійний спосіб відрізнити «не запитувався» від «заблоковано».
Показуйте обґрунтування лише один раз. Якщо користувач повторно відхилив запит після обґрунтування — більше не показуйте пояснення. Переходьте одразу до пропозиції відкрити налаштування. Багаторазовий показ обґрунтування сприймається як нав'язливість і знижує рейтинг додатка. Оптимальний сценарій: запит — відмова — обґрунтування — повторний запит — відмова — Налаштування.
Не показуйте обґрунтування до першого запиту. Деякі розробники помилково показують пояснення перед найпершим діалогом, аргументуючи це тим, що «користувач повинен зрозуміти». Це погіршує UX: користувач бачить два діалоги поспіль замість одного. Google рекомендує показувати системний діалог одразу, а обґрунтування — лише після відмови.
Використовуйте контекстне обґрунтування, прив'язане до моменту, коли функція дійсно потрібна. Не запитуйте всі дозволи при запуску додатка — це найнижчий показник надання. Запитуйте КАМЕРУ, коли користувач натиснув кнопку «Зробити фото», і МІСЦЕЗНАХОДЖЕННЯ, коли він відкрив карту. Контекстний запит у поєднанні з обґрунтуванням збільшує надання до 80 відсотків проти 30 відсотків при стартовому запиті.
Тестування shouldShowRequestPermissionRationale вимагає перевірки чотирьох станів таблиці: не запитувався, надано, відхилено, Never Ask Again. У юніт-тестах використовується FakePermissionHandler з налаштовуваною поведінкою shouldShowRationale. В інструментальних тестах — UiAutomator або Espresso з емуляцією відповідей на системні діалоги.
class RationaleViewModelTest {
private val handler = FakePermissionHandler()
private val viewModel = PermissionsViewModel(handler)
fun testFirstDenial_shouldShowRationale() {
handler.shouldShowRationale = true
handler.cameraResult =
PermissionResult.DENIED(true)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.Denied(true),
viewModel.uiState.value
)
}
fun testNeverAskAgain_redirectToSettings() {
handler.shouldShowRationale = false
handler.cameraResult =
PermissionResult.DENIED(false)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.RedirectToSettings,
viewModel.uiState.value
)
}
}
Ключовий сценарій для інструментального тесту — перевірка, що діалог обґрунтування дійсно відображається після першої відмови. Використовуйте Espresso з idling resources для очікування системного діалогу, потім натисніть Відхилити, перевірте появу кастомного діалогу з текстом пояснення та натисніть Дозволити — перевірте надання. UIAutomator дозволяє взаємодіяти з системним діалогом за текстом кнопки, що робить тест більш стабільним.
Також варто протестувати сценарій відмови всередині діалогу обґрунтування. Якщо користувач натиснув Відхилити в кастомному поясненні, shouldShowRequestPermissionRationale має знову повернути true, оскільки Never Ask Again ще не активовано. Найкраща практика — після двох відмов поспіль одразу перенаправляти в Налаштування, щоб не дратувати користувача повторними поясненнями та не знижувати рейтинг додатка.
Часті запитання
true — якщо запит відхилявся раніше і не встановлено Never Ask Again. false — якщо дозвіл не запитувався, надано або заблоковано назавжди. Комбінація false + DENIED вимагає перевірки через додатковий прапорець.
Показуйте обґрунтування лише після першої відмови користувача, коли shouldShowRequestPermissionRationale повернув true. До першого запиту обґрунтування не потрібне — це погіршує UX і створює зайві діалоги.
Зберігайте прапорець isFirstRequest у SharedPreferences або SavedStateHandle. Якщо shouldShowRationale = false, checkSelfPermission = DENIED і прапорець = true — значить активовано Never Ask Again. Якщо прапорець = false — це перший запит.
Покажіть діалог з кнопкою «Відкрити налаштування», який перенаправляє користувача в ACTION_APPLICATION_DETAILS_SETTINGS. Не викликайте requestPermissions повторно — діалог не з'явиться, а результат прийде з DENIED без повідомлення.
У юніт-тестах використовуйте FakePermissionHandler з налаштовуваним полем shouldShowRationale. В інструментальних тестах — Espresso або UIAutomator з емуляцією системного діалогу. Перевіряйте всі 4 стани з таблиці.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також