Get started with Grant in 5 minutes!
- Kotlin: 2.0.0 or higher
- Compose Multiplatform: 1.6.0 or higher (optional)
- Android: MinSDK 24 (Android 7.0)
- iOS: iOS 13.0 or higher
See Installation Guide for detailed setup instructions.
import dev.brewkits.grant.*
// In your ViewModel, Repository, or Composable
val grantManager = GrantFactory.create(context)That's it! No Fragment, no Activity, no binding needed.
suspend fun checkCameraPermission() {
val status = grantManager.checkStatus(AppGrant.CAMERA)
when (status) {
GrantStatus.GRANTED -> {
// Permission granted - proceed with camera
openCamera()
}
GrantStatus.NOT_DETERMINED -> {
// Never asked before - safe to request
println("Permission not requested yet")
}
GrantStatus.DENIED -> {
// User denied, but can ask again
// Show rationale and request again
showRationale()
}
GrantStatus.DENIED_ALWAYS -> {
// Permanently denied (user clicked "Don't ask again")
// Must open Settings
showSettingsPrompt()
}
}
}suspend fun requestCameraPermission() {
val status = grantManager.request(AppGrant.CAMERA)
when (status) {
GrantStatus.GRANTED -> openCamera()
GrantStatus.DENIED -> showRationale()
GrantStatus.DENIED_ALWAYS -> grantManager.openSettings()
GrantStatus.NOT_DETERMINED -> { /* Shouldn't happen after request */ }
}
}@Composable
fun CameraButton() {
val context = LocalContext.current
val grantManager = remember { GrantFactory.create(context) }
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
when (grantManager.request(AppGrant.CAMERA)) {
GrantStatus.GRANTED -> openCamera()
GrantStatus.DENIED -> showRationale()
GrantStatus.DENIED_ALWAYS -> grantManager.openSettings()
GrantStatus.NOT_DETERMINED -> {}
}
}
}
) {
Text("Take Photo")
}
}@Composable
fun CameraFeature() {
val grantManager = rememberGrantManager()
val scope = rememberCoroutineScope()
var cameraStatus by remember { mutableStateOf(GrantStatus.NOT_DETERMINED) }
// Check status on composition
LaunchedEffect(Unit) {
cameraStatus = grantManager.checkStatus(AppGrant.CAMERA)
}
Column {
Text("Camera Status: $cameraStatus")
Button(
onClick = {
scope.launch {
cameraStatus = grantManager.request(AppGrant.CAMERA)
}
},
enabled = cameraStatus != GrantStatus.GRANTED
) {
Text(when (cameraStatus) {
GrantStatus.GRANTED -> "Camera Ready"
GrantStatus.DENIED_ALWAYS -> "Open Settings"
else -> "Request Camera"
})
}
}
}suspend fun requestMediaPermissions() {
// Request camera first
if (grantManager.request(AppGrant.CAMERA) != GrantStatus.GRANTED) {
return // User denied camera
}
// Then microphone
if (grantManager.request(AppGrant.MICROPHONE) != GrantStatus.GRANTED) {
return // User denied microphone
}
// All granted!
startMediaRecording()
}suspend fun requestAllMediaPermissions() {
val handler = GrantGroupHandler(
grantManager = grantManager,
grants = listOf(
AppGrant.CAMERA,
AppGrant.MICROPHONE,
AppGrant.GALLERY
)
)
val results = handler.requestAll()
when {
results.allGranted -> {
// All permissions granted
startApp()
}
results.allDeniedAlways -> {
// All permanently denied
grantManager.openSettings()
}
else -> {
// Mixed results
println("Granted: ${results.grantedGrants}")
println("Denied: ${results.deniedGrants}")
}
}
}Grant includes built-in service checking:
val serviceManager = ServiceFactory.create(context)
// Check if GPS is enabled
if (!serviceManager.isLocationEnabled()) {
// GPS is off - open settings
serviceManager.openLocationSettings()
return
}
// GPS is on - now check permission
val status = grantManager.request(AppGrant.LOCATION)@Composable
fun SmartCameraButton() {
val grantManager = rememberGrantManager()
val scope = rememberCoroutineScope()
var showRationale by remember { mutableStateOf(false) }
if (showRationale) {
AlertDialog(
onDismissRequest = { showRationale = false },
title = { Text("Camera Permission") },
text = { Text("We need camera access to take photos for your profile") },
confirmButton = {
TextButton(onClick = {
showRationale = false
scope.launch {
grantManager.request(AppGrant.CAMERA)
}
}) {
Text("Grant")
}
},
dismissButton = {
TextButton(onClick = { showRationale = false }) {
Text("Cancel")
}
}
)
}
Button(onClick = {
scope.launch {
val status = grantManager.checkStatus(AppGrant.CAMERA)
when (status) {
GrantStatus.GRANTED -> openCamera()
GrantStatus.NOT_DETERMINED -> {
// First time - request directly
grantManager.request(AppGrant.CAMERA)
}
GrantStatus.DENIED -> {
// Show rationale before requesting again
showRationale = true
}
GrantStatus.DENIED_ALWAYS -> {
// Must open settings
grantManager.openSettings()
}
}
}
}) {
Text("Take Photo")
}
}suspend fun startLocationTracking(): Boolean {
val serviceManager = ServiceFactory.create(context)
// Step 1: Check service
if (!serviceManager.isLocationEnabled()) {
withContext(Dispatchers.Main) {
showDialog("Please enable GPS in Settings")
}
serviceManager.openLocationSettings()
return false
}
// Step 2: Check permission
val status = grantManager.request(AppGrant.LOCATION)
if (status != GrantStatus.GRANTED) {
withContext(Dispatchers.Main) {
if (status == GrantStatus.DENIED_ALWAYS) {
showDialog("Location permission denied. Please enable in Settings")
grantManager.openSettings()
} else {
showDialog("Location permission is required")
}
}
return false
}
// All good!
return true
}- Android Setup - Configure Android permissions
- iOS Setup - Configure Info.plist
- Permission Guide - Deep dive into permissions
- Best Practices - Production-ready patterns
Android: Check AndroidManifest.xml - ensure permission is declared
iOS: Check Info.plist - ensure usage description is added
Android: Ensure you're calling from a coroutine scope iOS: Check Xcode console for errors
This should not happen with Grant! If it does, please file an issue.
See Dead Click Fix for technical details.