shouldShowRequestPermissionRationale — е Android API метод, който подсказва на разработчика дали трябва да покаже обяснение на потребителя, преди да поиска опасно разрешение. Според Android Developer Reference, 2024, rational методът връща true, ако потребителят преди е отказал заявката, но не е задал флаг Never Ask Again. Това е клучов инструмент за изграждане на вежливо UX при работа с runtime разрешения.
Основни поенти
shouldShowRequestPermissionRationale — е метод на класовете Activity и Fragment в Android, достъпен чрез ActivityCompat за съвместимост. Той приема името на разрешението и връща Boolean, показващ дали трябва да се покаже допълнително обяснение на потребителя преди повторна заявка. Методът се появи в Android 6.0 Marshmallow заедно с runtime модела на разрешения.
Механизмът на rationale е изграден върху проследяване на историята на взаимодействие на потребителя с диалози за разрешения. Системата запомня дали потребителят преди е отказал заявката. Ако отказът е станал без задаване на флаг Never Ask Again, shouldShowRequestPermissionRationale връща true. Това е сигнал за разработчика: потребителят не разбира защо е нужно разрешението и се изисква допълнително обяснение. Според Google Material Design Guidelines, показването на rationale диалог след първия отказ увеличава вероятността за повторно предоставяне на разрешението с 35 процента.
Важно е да разберете семантиката на връщаните стойности: true означава, че има смисъл да покажете диалога, false — диалогът или не е нужен (разрешението вече е предоставено или никога не е било поисквано), или е безполезен (Never Ask Again е активен). Методът не е гаранция, че диалогът ще бъде показан — той просто дава препоръка. Разработчикът сам решава какво UI да покаже като отговор.
shouldShowRequestPermissionRationale беше введен в API Level 23 заедно с група методи за runtime разрешения. Преди Android 6.0, всички разрешения се изискваха при инсталация и механизъм за обяснение не беше нужен — потребителят приемаше или отказваше цялия списък наведнъж. Runtime моделът направи възможна ситуацията, при която потребителят отказва заявката, без да разбира контекста и именно затова е нужен rationale.
Логиката на метода е следната. При първото повикване на requestPermissions за конкретно разрешение, shouldShowRequestPermissionRationale връща false — потребителят все още не е срещал диалога. Ако потребителят откаже заявката (натисне Deny), методът започва да връща true. След повторно отказване с флаг Never Ask Again, методът връща false.
Пълна таблица на състоянията:
| Състояние | shouldShowRationale | checkSelfPermission | Действие на разработчика |
|---|---|---|---|
| Не е поисквано | false | DENIED | Покажете системен диалог |
| Предоставено | false | GRANTED | Изпълнете функцията |
| Отказано първи път | true | DENIED | Покажете rationale, след това системен диалог |
| Never Ask Again | false | DENIED | Пренасочете към Настройки |
Комбинацията shouldShowRequestPermissionRationale = false и checkSelfPermission = DENIED — е най-трудният за обработка случай. Означава, че или разрешението никога не е било поисквано, или Never Ask Again е зададено. Разработчикът трябва да различава тези две състояния. Единственият начин — съхранение на флаг firstRequest в SharedPreferences или използване на SavedStateHandle. При първата заявка задайте флага и ако shouldShowRationale е врънал false, а флагът вече е true — това означава Never Ask Again.
shouldShowRequestPermissionRationale се нулира, ако потребителят премахне и преинсталира приложението, изчисти данните на приложението или нулира настройките за разрешения. След преинсталиране, методът ще върне отново false за първата заявка. Системните обновления и промяната на версията на Android не нулират историята — тя се съхранява в данните на приложението.
Правилната имплементация на rationale включва три компонента: проверка на 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 препоръчват използването на bottom sheet или inline банер вместо на модален диалог за rationale. Bottom sheet е по-малко навъзлив и дава контекст на потребителя. Инлайн елементът на екрана (например, картичка с обяснение и бутон Позволи) показва, че функцията е недостъпна без разрешение, но не блокира останалия интерфейс.
Текстът на rationale трябва да бъде локализиран и адаптиран към конкретната функция. Не използвайте общи фрази като „Това е необходимо за работата на приложението”. Посочете конкретно: „За показване на времето наблизо до вас” или „За запазване на снимки в галерията”. Според Google UX Research, конкретните обяснения увеличават вероятността за предоставяне на разрешението с 50 процента.
Разликата между shouldShowRequestPermissionRationale = true (първи отказ) и false при DENIED (Never Ask Again) — е ключов момент в обработката на разрешенията. В първия случай потребителят колебаеше и допълнително обяснение може да го убеди да предостави достъп. Във втория — потребителят е взел окончателно решение и повторен системен диалог ще предизвика само раздражение.
Алгоритъмът за обработка след отказ трябва да изглежда така:
Важно е да не бъркате реда: първо проверете shouldShowRequestPermissionRationale, а не checkSelfPermission. checkSelfPermission така или иначе ще върне DENIED и в двата случая. Само shouldShowRequestPermissionRationale различава първия отказ от Never Ask Again. Използвайте SavedStateHandle или SharedPreferences за съхранение на флага „първата заявка е била” — това е единственият надежен начин да различавате „не е поисквано” от „блокирано”.
Показвайте rationale само веднъж. Ако потребителят отново откаже заявката след rationale — не показвайте повече обяснение. Преминете директно към предложението за отваряне на настройките. Повторното показване на rationale се възприема като навъзливост и намалява рейтинга на приложението. Оптимален сценарий: заявка — отказ — rationale — повторна заявка — отказ — Настройки.
Не показвайте rationale преди първата заявка. Някои разработчици грешко показват обяснение преди първия диалог, аргументирайки, че „потребителят трябва да разбира”. Това влошава UX: потребителят вижда два последователни диалоза вместо един. Google препоръчва да покажете системния диалог веднага, а rationale единствено след отказ.
Използвайте контекстуален rationale, свързан с момента, когато функцията наистина е необходима. Не изисквайте всички разрешения при стартиране на приложението — това е най-ниският процент на предоставяне. Изисквайте CAMERA, когато потребителят натисне бутона „Заснимане на снимка”, и LOCATION, когато отвори картата. Контекстуалната заявка в комбинация с rationale увеличава предоставянето на 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
)
}
}
Ключовият сценарий за инструменталния тест — проверка, че rationale диалогът наистина се появява след първия отказ. Използвайте Espresso с idling resources за да изчакате системния диалог, след това натиснете Deny, проверете появата на персонализирания диалог с текста на обяснението и натиснете Allow — проверете предоставянето. UIAutomator позволява взаимодействие с системния диалог чрез текста на бутона, което прави теста по-стабилен.
Също така си заслужава да се тества сценарийтът на отказ внутре в rationale диалога. Ако потребителят натисне Deny в персонализираното обяснение, shouldShowRequestPermissionRationale трябва отново да върне true, тъй като Never Ask Again все още не е активирано. Най-добра практика — след два последователни отказа директно да пренасочите към Настройки, за да не досаждате на потребителя с повторни обяснения и да не намалявате рейтинга на приложението.
Често задавани въпроси
true — ако заявката преди е била отказана и Never Ask Again не е зададено. false — ако разрешението не е било поисквано, е предоставено или е завинаги блокирано. Комбинацията false + DENIED изисква проверка чрез допълнителен флаг.
Показвайте rationale само след първия отказ на потребителя, когато shouldShowRequestPermissionRationale връне true. Преди първата заявка rationale не е нужен — това влошава 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също