Permission Handler в Android: як влаштовано, обробка запитів та реалізація

Автор: IT Sectr Опубліковано: 2026-05-20 Час читання: 8 хв

Permission Handler — це компонент Android-застосунку, що відповідає за перевірку, запит та обробку результатів runtime-дозволів. Згідно з Android Developer Guide, 2024, обробник дозволів централізує логіку checkSelfPermission, requestPermissions та shouldShowRequestPermissionRationale в єдиному класі або ViewModel. Це спрощує підтримку коду та покращує тестування.

Головне

  • Permission Handler — спеціалізований компонент для централізованого керування runtime-дозволами Android.
  • Інкапсулює логіку checkSelfPermission, requestPermissions та shouldShowRequestPermissionRationale.
  • Сучасні реалізації базуються на ActivityResultContracts з androidx.activity.
  • Спрощує юніт-тестування завдяки інверсії залежностей та ізоляції платформеного коду.
  • Кращі практики включають єдиний handler на Activity та перевикористання через DI-контейнер.

Що таке Permission Handler в Android

Permission Handler — це архітектурний патерн для централізованого керування runtime-дозволами Android. Замість розрізнених викликів ContextCompat.checkSelfPermission та ActivityCompat.requestPermissions по всьому коду застосунку, вся логіка запитів та обробки результатів зосереджується в одному класі. Це зменшує дублювання, спрощує підтримку та робить код більш передбачуваним.

Необхідність у Permission Handler виникла з впровадженням runtime-дозволів в Android 6.0. До цього всі дозволи запитувалися під час встановлення, і код застосунку міг використовувати будь-які API без перевірок. Після переходу на runtime-модель кожне використання небезпечного дозволу вимагає триетапної перевірки: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Розмазування цієї логіки по Activity та Fragment призводить до інлайн-дублювання та помилок. За даними Google I/O 2019, централізація обробки дозволів знижує кількість багів, пов'язаних з Permission Denial, в середньому на 60 відсотків.

Хороший Permission Handler надає чистий інтерфейс для коду, що викликає. Activity або Fragment не повинні знати деталі запиту — вони викликають метод типу requestCamera(callback), а handler сам керує перевіркою статусу, відображенням rationale, викликом системного діалогу та передачею результату в callback. Це реалізує принцип єдиної відповідальності та відокремлює бізнес-логіку від платформеного коду дозволів.

Коли потрібен Permission Handler

Permission Handler стає необхідним, коли застосунок використовує 3 і більше небезпечних дозволів. Для простих застосунків з одним дозволом (наприклад, камера для сканера QR-кодів) можна обійтися прямим викликом. Але для типового мобільного застосунку з камерою, геолокацією, сповіщеннями та сховищем — централізований handler обов'язковий для підтримки.

Архітектура Permission Handler

Типовий Permission Handler складається з трьох рівнів: інтерфейс-контракт, реалізація з ActivityResultLauncher та рівень для ViewModel. Інтерфейс визначає методи запиту для кожного дозволу — requestCamera, requestLocation, requestStorage. Реалізація пов'язує ці методи з відповідними контрактами ActivityResultContracts.RequestPermission.

Ключові компоненти архітектури:

  • PermissionHandlerContract — інтерфейс з методами для кожного дозволу
  • PermissionHandlerImpl — реалізація, що з'єднується з ActivityResultRegistry
  • PermissionResult — sealed class зі станами GRANTED, DENIED, NEVER_ASK_AGAIN
  • RationaleHandler — компонент для відображення пояснень перед запитом

Така архітектура дозволяє легко підміняти реалізацію в тестах: замість реального ActivityResultLauncher використовується мок, який повертає попередньо визначений результат без взаємодії з системою. Це критично важливо для юніт-тестування UI-логіки, де запуск Activity для діалогу дозволів неможливий.

Керування життєвим циклом

Permission Handler повинен враховувати життєвий цикл Activity та Fragment. Лаунчери реєструються в ActivityResultRegistry, який автоматично зберігає та відновлює стан при повороті екрану та перестворенні Activity. Handler не повинен зберігати прямі посилання на Activity або Fragment — натомість використовуйте WeakReference або передавайте registry через конструктор. Це запобігає витокам пам'яті та крашам при configuration changes.

Реалізація Permission Handler в Kotlin

Базова реалізація Permission Handler будується на ActivityResultContracts.RequestPermission. Handler отримує ActivityResultRegistry з ComponentActivity або Fragment та реєструє лаунчери для кожного дозволу. Кожен лаунчер приймає лямбду-колбек, яка викликається після відповіді користувача.

kotlin
sealed class PermissionResult {
    object GRANTED : PermissionResult()
    data class DENIED(
        val shouldShowRationale: Boolean
    ) : PermissionResult()
}

interface PermissionHandler {
    fun requestCamera(
        callback: (PermissionResult) -> Unit
    )
    fun requestLocation(
        callback: (PermissionResult) -> Unit
    )
    fun isPermissionGranted(
        permission: String
    ): Boolean
}

class AndroidPermissionHandler(
    private val registry: ActivityResultRegistry,
    private val context: Context
) : PermissionHandler {

    private var cameraLauncher: ActivityResultLauncher<String>? = null

    fun initialize() {
        cameraLauncher = registry.register(
            "camera_permission",
            ActivityResultContracts.RequestPermission()
        ) { isGranted ->
            if (isGranted) {
                pendingCameraCallback?.invoke(
                    PermissionResult.GRANTED
                )
            } else {
                val rationale = ActivityCompat.shouldShowRequestPermissionRationale(
                    context as Activity,
                    Manifest.permission.CAMERA
                )
                pendingCameraCallback?.invoke(
                    PermissionResult.DENIED(rationale)
                )
            }
        }
    }

    private var pendingCameraCallback:
        ((PermissionResult) -> Unit)? = null

    override fun requestCamera(
        callback: (PermissionResult) -> Unit
    ) {
        if (isPermissionGranted(
                Manifest.permission.CAMERA
        )) {
            callback.invoke(PermissionResult.GRANTED)
            return
        }
        pendingCameraCallback = callback
        cameraLauncher?.launch(
            Manifest.permission.CAMERA
        )
    }

    override fun isPermissionGranted(
        permission: String
    ): Boolean {
        return ContextCompat.checkSelfPermission(
            context, permission
        ) == PackageManager.PERMISSION_GRANTED
    }
}

Ініціалізація в Activity

Handler ініціалізується в onCreate Activity через registerForActivityResult, який надає доступ до ActivityResultRegistry. Після ініціалізації handler готовий обробляти запити протягом всього життєвого циклу Activity. Важливо викликати initialize до першого запиту, інакше лаунчер не буде зареєстровано.

Permission Handler з ViewModel

Інтеграція Permission Handler з ViewModel — найбільш просунутий підхід. ViewModel керує станом запитів, а Handler виконує лише платформені виклики. ViewModel містить StateFlow<PermissionUiState>, де UiState описує, який дозвіл запитується та який результат отримано. Activity підписується на цей StateFlow та делегує запит Handler.

kotlin
class PermissionsViewModel : ViewModel() {

    private val _uiState =
        MutableStateFlow<PermissionUiState>(
            PermissionUiState.Idle
        )
    val uiState: StateFlow<PermissionUiState> = _uiState.asStateFlow()

    fun onCameraRequested() {
        _uiState.value = PermissionUiState.RequestingCamera
    }

    fun onPermissionResult(
        permission: String,
        result: PermissionResult
    ) {
        when (result) {
            PermissionResult.GRANTED -> {
                _uiState.value = PermissionUiState.Granted(permission)
            }
            is PermissionResult.DENIED -> {
                _uiState.value = PermissionUiState.Denied(
                    permission,
                    result.shouldShowRationale
                )
            }
        }
    }
}

sealed class PermissionUiState {
    object Idle : PermissionUiState()
    object RequestingCamera : PermissionUiState()
    data class Granted(val permission: String) : PermissionUiState()
    data class Denied(
        val permission: String,
        val shouldShowRationale: Boolean
    ) : PermissionUiState()
}

У цій моделі Activity перевіряє isPermissionGranted через Handler при старті, а ViewModel керує лише станом. Якщо дозвіл не надано — Activity підписується на uiState, викликає requestCamera з Handler і передає результат назад у ViewModel через onPermissionResult. Розділення платформеного коду та бізнес-логіки дозволяє тестувати ViewModel без Android-залежностей.

Тестування Permission Handler

Юніт-тестування Permission Handler можливе завдяки інтерфейсу PermissionHandler. У тестах створюється FakePermissionHandler, який імітує різні сценарії: дозвіл надано, відхилено, Never Ask Again. Кожен сценарій перевіряється незалежно. Це особливо важливо для тестування UI-логіки, яка повинна коректно реагувати на всі три результати.

kotlin
class FakePermissionHandler : PermissionHandler {

    var cameraResult: PermissionResult =
        PermissionResult.GRANTED
    var grantedPermissions: Set<String> =
        setOf(Manifest.permission.CAMERA)

    override fun requestCamera(
        callback: (PermissionResult) -> Unit
    ) {
        callback.invoke(cameraResult)
    }

    override fun isPermissionGranted(
        permission: String
    ): Boolean {
        return permission in grantedPermissions
    }
}

Fake-реалізація дозволяє тестувати ViewModel без емулятора. Достатньо встановити cameraResult у потрібне значення та перевірити, що ViewModel коректно оновлює UiState. Інтеграційні тести перевіряють реальний PermissionHandler з ActivityScenario, але таких тестів зазвичай 2-3 на весь застосунок — решта сценаріїв покриваються юніт-тестами з фейками.

Поширені патерни та помилки

Типові помилки при роботі з Permission Handler включають: відсутність перевірки checkSelfPermission перед кожним викликом API, ігнорування shouldShowRequestPermissionRationale, повторний виклик requestPermissions при Never Ask Again та зберігання лаунчерів без урахування життєвого циклу Activity. Розглянемо кожну проблему та рішення.

Найпоширеніша помилка — виклик API без перевірки статусу дозволу. Розробники припускають, що якщо дозвіл було надано один раз, він залишиться назавжди. Однак користувач може відкликати його через налаштування в будь-який момент. Permission Handler повинен завжди викликати isPermissionGranted перед виконанням чутливої операції. Друга популярна помилка — ігнорування shouldShowRequestPermissionRationale та повторний запит, який призводить до миттєвої відмови без діалогу при Never Ask Again.

Кращі практики включають: створення одного екземпляра Handler на весь життєвий цикл Activity, використання SharedFlow для передачі результатів у ViewModel, логування всіх запитів та відмов для аналітики, а також показ кастомного rationale-діалогу перед системним при першій відмові. Дотримання цих правил гарантує стабільну роботу з дозволами на всіх версіях Android.

Поширені запитання

Що таке Permission Handler в Android?

Permission Handler — це компонент для централізованого керування runtime-дозволами, що інкапсулює checkSelfPermission, requestPermissions та shouldShowRequestPermissionRationale. Він спрощує підтримку коду та покращує тестування.

Який API використовувати для Handler у 2024?

Рекомендується ActivityResultContracts.RequestPermission з бібліотеки androidx.activity. Він замінює застарілий onRequestPermissionsResult та надає чистий callback API з результатом Boolean.

Чи потрібен Handler для одного дозволу?

Для одного дозволу Handler не обов'язковий — можна використовувати прямий виклик RequestPermission лаунчера в Activity. Handler стає необхідним при 3 і більше дозволах, щоб уникнути дублювання коду.

Як тестувати Permission Handler?

Створіть інтерфейс PermissionHandler та його fake-реалізацію для юніт-тестів. Fake повертає попередньо визначені результати без системних викликів. Це дозволяє тестувати ViewModel та UI-логіку без емулятора.

Як обробити Never Ask Again у Handler?

Після відмови перевірте shouldShowRequestPermissionRationale. Якщо метод повернув false — активовано режим Never Ask Again. Handler повинен повернути PermissionResult.DENIED(false), а UI — показати кнопку переходу в Settings.

Підсумки

  • Permission Handler — архітектурний компонент для централізованого керування runtime-дозволами Android.
  • Базується на ActivityResultContracts.RequestPermission з бібліотеки androidx.activity.
  • Інтерфейс з методами для кожного дозволу спрощує юніт-тестування через fake-реалізації.
  • Інтеграція з ViewModel через StateFlow розділяє платформенний код та бізнес-логіку.
  • Типові помилки: відсутність checkSelfPermission, ігнорування rationale та Never Ask Again.
  • Краща практика — один Handler на Activity з реєстрацією в onCreate.
  • Централізація знижує кількість Permission Denial-багів на 60 відсотків у типових проектах.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також