Permission Handler je komponenta aplikace pro Android odpovědná za kontrolu, žádání a zpracování výsledků runtime oprávnění. Podle Android Developer Guide, 2024, obsluha oprávnění centralizuje logiku checkSelfPermission, requestPermissions a shouldShowRequestPermissionRationale do jediné třídy nebo ViewModelu. To zjednodušuje údržbu kódu a zlepšuje testování.
Hlavní body
Permission Handler — je architektonický vzor pro centralizovanou správu runtime oprávnění Androidu. Namísto roztroušených volání ContextCompat.checkSelfPermission a ActivityCompat.requestPermissions v celém kódu aplikace je veškerá logika žádostí a zpracování výsledků soustředěna do jedné třídy. To snižuje duplicitu, zjednodušuje údržbu a činí kód předvídatelnějším.
Potřeba Permission Handleru vznikla se zavedením runtime oprávnění v Androidu 6.0. Předtím byla všechna oprávnění vyžadována při instalaci a kód aplikace mohl používat jakékoli API bez kontrol. Po přechodu na runtime model vyžaduje každé použití nebezpečného oprávnění třífázovou kontrolu: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Rozptýlení této logiky mezi Activity a Fragment vede k inline duplicitě a chybám. Podle údajů Google I/O 2019 centralizace zpracování oprávnění snižuje počet chyb souvisejících s Permission Denial v průměru o 60 procent.
Dobrý Permission Handler poskytuje čisté rozhraní pro volající kód. Activity nebo Fragment nemusí znát podrobnosti požadavku — volají metodu typu requestCamera(callback) a handler sám spravuje kontrolu stavu, zobrazení rationale, volání systémového dialogu a předání výsledku do callbacku. To implementuje princip jediné odpovědnosti a odděluje business logiku od platformního kódu oprávnění.
Permission Handler se stává nezbytným, když aplikace používá 3 nebo více nebezpečných oprávnění. Pro jednoduché aplikace s jedním oprávněním (například kamera pro skener QR kódů) vystačíte s přímým voláním. Ale pro typickou mobilní aplikaci s kamerou, geolokací, notifikacemi a úložištěm — centralizovaný handler je pro údržbu povinný.
Typický Permission Handler se skládá ze tří úrovní: rozhraní-kontrakt, implementace s ActivityResultLauncherem a vrstva pro ViewModel. Rozhraní definuje metody žádostí pro každé oprávnění — requestCamera, requestLocation, requestStorage. Implementace propojuje tyto metody s odpovídajícími kontrakty ActivityResultContracts.RequestPermission.
Klíčové komponenty architektury:
Tato architektura umožňuje snadno nahradit implementaci v testech: místo skutečného ActivityResultLauncheru se používá mock, který vrací předdefinovaný výsledek bez interakce se systémem. To je kritické pro unit testování UI logiky, kde spuštění Activity pro dialog oprávnění není možné.
Permission Handler musí brát v úvahu životní cyklus Activity a Fragmentu. Launcherové jsou registrováni v ActivityResultRegistry, který automaticky ukládá a obnovuje stav při otočení obrazovky a obnovení Activity. Handler by neměl uchovávat přímé reference na Activity nebo Fragment — místo toho použijte WeakReference nebo předejte registry přes konstruktor. To zabraňuje únikům paměti a pádům při změnách konfigurace.
Základní implementace Permission Handleru je postavena na ActivityResultContracts.RequestPermission. Handler získává ActivityResultRegistry z ComponentActivity nebo Fragmentu a registruje launcherové pro každé oprávnění. Každý launcherov přijímá lambda-callback, který je volán po odpovědi uživatele.
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
}
}
Handler je inicializován v onCreate Activity prostřednictvím registerForActivityResult, který poskytuje přístup k ActivityResultRegistry. Po inicializaci je handler připraven zpracovávat požadavky po celý životní cyklus Activity. Je důležité zavolat initialize před prvním požadavkem, jinak nebude launcherov registrován.
Integrace Permission Handleru s ViewModelem je nejpokročilejší přístup. ViewModel spravuje stav požadavků a Handler provádí pouze platformní volání. ViewModel obsahuje StateFlow<PermissionUiState>, kde UiState popisuje, které oprávnění je požadováno a jaký výsledek byl získán. Activity se přihlásí k odběru tohoto StateFlow a deleguje požadavek na Handler.
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()
}
V tomto modelu Activity při startu kontroluje isPermissionGranted přes Handler, zatímco ViewModel spravuje pouze stav. Pokud oprávnění nebylo uděleno — Activity se přihlásí k odběru uiState, zavolá requestCamera z Handleru a předá výsledek zpět do ViewModelu přes onPermissionResult. Oddělení platformního kódu a business logiky umožňuje testovat ViewModel bez Android závislostí.
Unit testování Permission Handleru je možné díky rozhraní PermissionHandler. V testech se vytváří FakePermissionHandler, který simuluje různé scénáře: oprávnění uděleno, zamítnuto, Never Ask Again. Každý scénář je testován nezávisle. To je obzvláště důležité pro testování UI logiky, která musí správně reagovat na všechny tři výsledky.
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 implementace umožňuje testovat ViewModel bez emulátoru. Stačí nastavit cameraResult na požadovanou hodnotu a zkontrolovat, že ViewModel správně aktualizuje UiState. Integrační testy kontrolují skutečný PermissionHandler s ActivityScenario, ale obvykle jsou 2-3 takové testy pro celou aplikaci — ostatní scénáře jsou pokryty unit testy s fake implementacemi.
Typické chyby při práci s Permission Handlerem zahrnují: chybějící kontrolu checkSelfPermission před každým API voláním, ignorování shouldShowRequestPermissionRationale, opakované volání requestPermissions při Never Ask Again a uchovávání launcherovů bez ohledu na životní cyklus Activity. Podívejme se na každý problém a jeho řešení.
Nejčastější chyba — volání API bez kontroly stavu oprávnění. Vývojáři předpokládají, že pokud bylo oprávnění jednou uděleno, zůstane navždy. Uživatel jej však může kdykoli odvolat v nastavení. Permission Handler musí vždy volat isPermissionGranted před provedením citlivé operace. Druhou častou chybou je ignorování shouldShowRequestPermissionRationale a opakovaný požadavek, který vede k okamžitému zamítnutí bez dialogu při Never Ask Again.
Nejlepší postupy zahrnují: vytvoření jedné instance Handleru pro celý životní cyklus Activity, použití SharedFlow pro předávání výsledků do ViewModelu, logování všech požadavků a zamítnutí pro analytiku, a také zobrazení vlastního rationale dialogu před systémovým při prvním zamítnutí. Dodržování těchto pravidel zaručuje stabilní práci s oprávněními na všech verzích Androidu.
Často kladené otázky
Permission Handler — komponenta pro centralizovanou správu runtime oprávnění, zapouzdřující checkSelfPermission, requestPermissions a shouldShowRequestPermissionRationale. Zjednodušuje údržbu kódu a zlepšuje testování.
Doporučuje se ActivityResultContracts.RequestPermission z knihovny androidx.activity. Nahrazuje zastaralý onRequestPermissionsResult a poskytuje čisté callback API s výsledkem Boolean.
Pro jedno oprávnění Handler není povinný — můžete použít přímé volání RequestPermission laucherovu v Activity. Handler se stává nezbytným při 3 a více oprávněních, aby se předešlo duplicitě kódu.
Vytvořte rozhraní PermissionHandler a jeho fake implementaci pro unit testy. Fake vrací předdefinované výsledky bez systémových volání. To umožňuje testovat ViewModel a UI logiku bez emulátoru.
Po zamítnutí zkontrolujte shouldShowRequestPermissionRationale. Pokud metoda vrátila false — je aktivován režim Never Ask Again. Handler by měl vrátit PermissionResult.DENIED(false) a UI by mělo zobrazit tlačítko pro přechod do Nastavení.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také