GrantHandler
Encapsulates ALL grant logic in a clean, reusable component.
This class follows the Composition Pattern - ViewModels compose this handler instead of implementing grant logic themselves.
Benefits:
DRY: Write grant logic once, reuse everywhere
Clean ViewModels: Reduce from 30+ lines to 3 lines
Consistent UX: All features follow same grant flow
Testable: Easy to mock and test
Process Death Recovery: Optional state restoration on Android
Usage in ViewModel:
class CameraViewModel(grantManager: GrantManager) : ViewModel() {
val cameraGrant = GrantHandler(
grantManager = grantManager,
grant = AppGrant.CAMERA, // Or use RawPermission for custom permissions
scope = viewModelScope // CRITICAL: Use viewModelScope!
)
fun onCaptureClick() {
cameraGrant.request {
// Only runs when grant is GRANTED
openCamera()
}
}
}Usage in UI (Compose):
GrantDialogHandler(handler = viewModel.cameraGrant)Process Death Recovery (Android):
class CameraViewModel(
grantManager: GrantManager,
savedStateHandle: SavedStateHandle
) : ViewModel() {
val cameraGrant = GrantHandler(
grantManager = grantManager,
grant = AppGrant.CAMERA,
scope = viewModelScope,
savedStateDelegate = AndroidSavedStateDelegate(savedStateHandle) // Optional
)
}Parameters
The underlying grant manager
The specific grant this handler manages (AppGrant or RawPermission)
CoroutineScope for launching grant requests.
**CRITICAL - Scope Requirements:**
- **MUST** use [viewModelScope] from a ViewModel
- Survives configuration changes (screen rotation)
- Automatically cancelled when ViewModel is cleared
- Prevents memory leaks
**DO NOT USE:**
- [GlobalScope]: Never cancelled, causes memory leaks
- [lifecycleScope]: Cancelled on config changes, breaks ongoing requests
- Custom scopes with short lifecycle
- Any scope that doesn't outlive the grant flow
**Example of CORRECT usage:**
```kotlin
class MyViewModel : ViewModel() {
val handler = GrantHandler(..., scope = viewModelScope) // ✅ Correct
}
```
**Example of WRONG usage:**
```kotlin
class MyFragment : Fragment() {
val handler = GrantHandler(..., scope = lifecycleScope) // ❌ Wrong!
// This breaks on screen rotation!
}
```Optional delegate for saving/restoring state across process death. Use AndroidSavedStateDelegate on Android for automatic restoration. Defaults to NoOpSavedStateDelegate (no persistence).
Constructors
Functions
Called when user confirms rationale dialog and wants to proceed.
Call this when the app returns to foreground to check if user changed settings.
Called when user clicks "Open Settings" button
Refresh the current grant status. Call this after user returns from Settings.
A Flow-based alternative to request. Returns a Flow that executes the permission request when collected.
A suspending alternative to request. Suspends the current coroutine until the grant flow completes (granted, denied, or dismissed).
Request grant with custom UI callback handlers.