Skip to content

brewkits/Grant

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Grant Logo

Grant: Robust Permission Management for KMP

Production-ready, type-safe permission handling for Kotlin Multiplatform — handling the complex edge cases of Android and iOS flows.

Maven Central Kotlin Platform License


⚡ Zero Boilerplate. Zero Lifecycle Binding. Zero Headache.

Grant is not just another permission library. It is a production-hardened engine designed to handle complex edge cases that lead to crashes and hangs in other solutions. Built for professionals who demand absolute reliability.

Explore DocumentationQuick StartWhy Grant?Demo App


🚀 Killer Features

  • 🎯 Pure Logic-First API — Works anywhere: ViewModels, Repositories, or Composables. No Activity or Fragment references required.
  • 🍎 iOS Framework Isolation — Each permission type is isolated to its own handler, preventing unused Apple frameworks (Location, Bluetooth, Motion, etc.) from being linked into your binary. No more phantom NSUsageDescription requirements.
  • 🛡️ iOS Crash-Guard — Automatically validates Info.plist keys before requesting, preventing the dreaded SIGABRT production crashes.
  • 🔄 Android Process-Death Resilience — The only library that handles system-initiated process death gracefully with zero timeouts (via SavedStateHandle).
  • ⚡ Reentrant Locking — Custom ReentrantMutex prevents deadlocks in complex nested permission flows.
  • 📦 18 Native Permissions — Deep, native integration for Camera, Gallery, Location, Bluetooth, NEARBY_WIFI_DEVICES, and more.
  • 🎨 Modern M3 UI — Out-of-the-box support for Material 3 BasicAlertDialog in the Compose module.
  • 🧩 Custom Extensibility — Register your own iOS handlers via IosPermissionHandlerRegistry or use RawPermission.
  • 🧪 Ultra-Robust Testing800+ automated tests covering every platform edge case, state invariant, and UI interaction. 100% pass rate.

💎 The "Grant" Experience

1️⃣ Define your logic (Logic Layer)

class CameraViewModel(private val grantManager: GrantManager) : ViewModel() {
    val cameraGrant = GrantHandler(
        grantManager = grantManager,
        grant = AppGrant.CAMERA,
        scope = viewModelScope
    )

    // Option A: Suspend function (Modern Coroutines)
    suspend fun startCapture() {
        val status = cameraGrant.requestSuspend()
        if (status == GrantStatus.GRANTED) {
            cameraEngine.start()
        }
    }

    // Option B: Flow-based (Reactive)
    val captureFlow = cameraGrant.requestFlow()
        .filter { it == GrantStatus.GRANTED }
        .onEach { cameraEngine.start() }
}

2️⃣ Orchestrate Sequential Flows (GrantFlow DSL)

val scanFlow = grantFlow {
    // 1. First, we need Bluetooth
    val btStatus = bluetoothHandler.requestSuspend()
    
    // 2. If granted, we need Location (for scanning on some Android versions)
    if (btStatus == GrantStatus.GRANTED) {
        locationHandler.requestSuspend()
    }
}

3️⃣ Drop in the UI (Presentation Layer)

@Composable
fun CameraScreen(viewModel: CameraViewModel) {
    // Automatically uses Material 3 BasicAlertDialog for a modern look
    GrantDialog(handler = viewModel.cameraGrant)

    Button(onClick = { viewModel.viewModelScope.launch { viewModel.startCapture() } }) {
        Text("Start Camera")
    }
}

🏆 Best Practice: The Full Readiness Check (Logic + Hardware)

Permission is only half the battle. In production, you also need to check if the hardware service (GPS, Bluetooth) is actually enabled.

// Use GrantAndServiceChecker to combine both worlds
class LocationViewModel(
    private val checker: GrantAndServiceChecker,
    private val grantManager: GrantManager
) : ViewModel() {

    fun startTracking() {
        viewModelScope.launch {
            when (val status = checker.checkLocationReady()) {
                LocationReadyStatus.Ready          -> sensor.start()
                LocationReadyStatus.ServiceDisabled -> _uiState.showEnableGPS()
                LocationReadyStatus.GrantDenied    -> requestPermission()
                LocationReadyStatus.BothRequired   -> _uiState.showTotalFailure()
            }
        }
    }
}

⚔️ Why Grant?

Most KMP permission libraries are simple wrappers around native APIs. Grant is an Architectural Solution.

Feature Grant moko-permissions accompanist-permissions
No Lifecycle Binding ❌ (needs BindEffect) ❌ (needs Activity)
ViewModel Support Full Partial
iOS Crash Prevention
iOS Framework Isolation N/A
Android Deadlock Fix
Process Death Recovery Native Manual
Service Checks (GPS/BT/Health)
Android 14 Partial Access Partial
Custom Permissions Limited Limited

🗺️ Platform Support & Coverage

Permission Android iOS Notes
Camera iOS main-thread safe + deadlock fix
Microphone Shares AVFoundation handler with Camera
Gallery (full) Android 14+ partial access (PARTIAL_GRANTED)
Gallery (images only) AppGrant.GALLERY_IMAGES_ONLY
Gallery (video only) AppGrant.GALLERY_VIDEO_ONLY
Storage (legacy) Pre-API 33 fallback
Location (when in use) Intelligent GPS service check included
Location (always) Android 2-step background flow handled
Notifications Android 13+ and legacy flows
Bluetooth Service status check + Scan/Connect
Bluetooth Advertise AppGrant.BLUETOOTH_ADVERTISE
Contacts (full) Read + Write access
Contacts (read-only) AppGrant.READ_CONTACTS
Calendar (full) iOS 17+ FullAccess / WriteOnly mapped correctly
Calendar (read-only) AppGrant.READ_CALENDAR
Motion / Activity Simulator-aware (safe mock on Simulator)
Schedule Exact Alarm Android 12+ SCHEDULE_EXACT_ALARM

Service Checks (ServiceType)

Service Android iOS
GPS / Location
Bluetooth
Wi-Fi
NFC
Camera hardware
Health Connect / HealthKit

📦 Installation

// shared/build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("dev.brewkits:grant-core:2.0.0")
            implementation("dev.brewkits:grant-compose:2.0.0")         // Optional: Compose dialogs
            implementation("dev.brewkits:grant-core-koin:2.0.0")       // Optional: Koin DI support

            // Optional: add only the permission modules you actually use on iOS.
            // Omitting a module means its iOS framework is never linked — no phantom
            // NSUsageDescription keys, no App Store rejections.
            implementation("dev.brewkits:grant-contacts:2.0.0")        // Optional: Contacts (iOS CNContactStore)
            implementation("dev.brewkits:grant-calendar:2.0.0")        // Optional: Calendar (iOS EventKit)
            implementation("dev.brewkits:grant-motion:2.0.0")          // Optional: Motion (iOS CoreMotion)
        }
    }
}

iOS-only step: call initialize() once at app startup for each optional module you added:

// iOS — AppDelegate / @main entry point
GrantContacts.shared.initialize()   // if you added grant-contacts
GrantCalendar.shared.initialize()   // if you added grant-calendar
GrantMotion.shared.initialize()     // if you added grant-motion

Important

For projects targeting Web (JS) or Desktop (JVM), use an intermediate mobileMain source set to avoid linking iOS/Android dependencies on unsupported platforms. Read the Guide.

Note

Migrating from v1.x? Contacts, Calendar, and Motion permissions are now opt-in modules. Add the corresponding artifact and call initialize() on iOS. Android behavior is unchanged — no code changes required on Android. See the Migration Guide.


📖 Deep Dives

Guide Description
Architecture How concurrency, state machines, and the mutex flow work
iOS Setup Critical Info.plist configuration — read before shipping
Migration Guide Upgrading from v1.x to v2.0.0
Service Checking Combining permission + hardware service checks
Manual Injection Using Grant without any DI framework
Android Reliability How we fix "Dead Clicks" on Android
Best Practices Patterns for production apps

🤝 Contributing

We are on a mission to make permissions a "solved problem" for KMP. Join us!

  1. Check out CONTRIBUTING.md.
  2. Run ./gradlew :grant-core:allTests to ensure stability.
  3. Submit your PR.

⚖️ License

Grant is licensed under the Apache License 2.0. See LICENSE for details.

Built with ❤️ by BrewKits