{"id":49122652,"url":"https://github.com/brewkits/Grant","last_synced_at":"2026-05-07T19:00:58.974Z","repository":{"id":335317779,"uuid":"1139895555","full_name":"brewkits/Grant","owner":"brewkits","description":"Kotlin Multiplatform permission library for Android \u0026 iOS. No Fragment/Activity needed, ViewModel-first, Compose Multiplatform ready. Fixes Android dead clicks \u0026 iOS deadlocks.","archived":false,"fork":false,"pushed_at":"2026-04-29T02:00:28.000Z","size":1331,"stargazers_count":85,"open_issues_count":1,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-04-29T04:04:02.539Z","etag":null,"topics":["android","android-14-permission","bluetooth","clean-architecture","compose-multiplatform","compose-multiplatform-permission","coroutines","gps","ios","ios-17-permission","jetpack-compose","kmp","kotlin-multiplatform","kotlin-multiplatform-permission","moko-permissions-alternative","mvvm","nfc","permission","permissions","runtime-permissions"],"latest_commit_sha":null,"homepage":"https://www.brewkits.dev","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brewkits.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-22T15:01:45.000Z","updated_at":"2026-04-21T12:12:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"b58084ac-91af-4e3c-846e-c50eb67f43e3","html_url":"https://github.com/brewkits/Grant","commit_stats":null,"previous_names":["brewkits/grant"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/brewkits/Grant","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2FGrant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2FGrant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2FGrant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2FGrant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brewkits","download_url":"https://codeload.github.com/brewkits/Grant/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brewkits%2FGrant/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32751758,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-14-permission","bluetooth","clean-architecture","compose-multiplatform","compose-multiplatform-permission","coroutines","gps","ios","ios-17-permission","jetpack-compose","kmp","kotlin-multiplatform","kotlin-multiplatform-permission","moko-permissions-alternative","mvvm","nfc","permission","permissions","runtime-permissions"],"created_at":"2026-04-21T13:00:29.136Z","updated_at":"2026-05-07T19:00:58.967Z","avatar_url":"https://github.com/brewkits.png","language":"Kotlin","funding_links":[],"categories":["Libraries"],"sub_categories":["📱 Device"],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/logo.svg\" height=\"128\" alt=\"Grant Logo\" /\u003e\n\n# Grant: Robust Permission Management for KMP\n\n**Production-ready, type-safe permission handling for Kotlin Multiplatform — handling the complex edge cases of Android and iOS flows.**\n\n[![Maven Central](https://img.shields.io/maven-central/v/dev.brewkits/grant-core?color=7F52FF\u0026label=Maven%20Central\u0026style=for-the-badge)](https://central.sonatype.com/artifact/dev.brewkits/grant-core)\n[![Kotlin](https://img.shields.io/badge/Kotlin-2.1.0-7F52FF?logo=kotlin\u0026logoColor=white\u0026style=for-the-badge)](https://kotlinlang.org)\n[![Platform](https://img.shields.io/badge/Platform-Android%20%7C%20iOS-lightgrey?style=for-the-badge)](https://kotlinlang.org/docs/multiplatform.html)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=for-the-badge)](LICENSE)\n\n---\n\n### ⚡ Zero Boilerplate. Zero Lifecycle Binding. Zero Headache.\n\nGrant 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.\n\n[**Explore Documentation**](docs/README.md) • [**Quick Start**](#-quick-start) • [**Why Grant?**](#-why-grant) • [**Demo App**](#-demo)\n\n\u003c/div\u003e\n\n---\n\n## 🚀 Killer Features\n\n- **🎯 Pure Logic-First API** — Works anywhere: ViewModels, Repositories, or Composables. **No Activity or Fragment references required.**\n- **🍎 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.\n- **🛡️ iOS Crash-Guard** — Automatically validates `Info.plist` keys before requesting, preventing the dreaded `SIGABRT` production crashes.\n- **🔄 Android Process-Death Resilience** — The only library that handles system-initiated process death gracefully with zero timeouts.\n- **⚡ iOS Deadlock Fix** — Built-in protection against the infamous Camera/Microphone first-request deadlock.\n- **📦 17 Native Permissions** — Deep, native integration for Camera, Gallery (Partial access!), Location, Bluetooth, Motion, Health, and more.\n- **🛠️ Service Intelligence** — Don't just check permissions; check if services (GPS, Bluetooth, Health) are actually enabled.\n- **🧩 Custom Extensibility** — Use `RawPermission` to support new OS permissions (Android 15+, iOS 18+) instantly without library updates.\n- **🧪 Ultra-Robust Testing** — **782 automated tests** (423 Android + 359 iOS Simulator) covering every platform edge case, state invariant, and UI interaction. 100% pass rate.\n\n---\n\n## 💎 The \"Grant\" Experience\n\n### 1️⃣ Define your logic (Logic Layer)\n```kotlin\nclass CameraViewModel(private val grantManager: GrantManager) : ViewModel() {\n    val cameraGrant = GrantHandler(\n        grantManager = grantManager,\n        grant = AppGrant.CAMERA,\n        scope = viewModelScope\n    )\n\n    fun capturePhoto() {\n        cameraGrant.request {\n            // Only runs when permission is FULLY granted\n            cameraEngine.startCapture()\n        }\n    }\n}\n```\n\n### 2️⃣ Drop in the UI (Presentation Layer)\n```kotlin\n@Composable\nfun CameraScreen(viewModel: CameraViewModel) {\n    // Handles Rationale, Denied, and Settings dialogs automatically\n    GrantDialog(handler = viewModel.cameraGrant)\n\n    IconButton(onClick = { viewModel.capturePhoto() }) {\n        Icon(Icons.Default.Camera, contentDescription = \"Capture\")\n    }\n}\n```\n\n### 🏆 Best Practice: The Full Readiness Check (Logic + Hardware)\nPermission is only half the battle. In production, you also need to check if the hardware service (GPS, Bluetooth) is actually enabled.\n\n```kotlin\n// Use GrantAndServiceChecker to combine both worlds\nclass LocationViewModel(\n    private val checker: GrantAndServiceChecker,\n    private val grantManager: GrantManager\n) : ViewModel() {\n\n    fun startTracking() {\n        viewModelScope.launch {\n            when (val status = checker.checkLocationReady()) {\n                LocationReadyStatus.Ready          -\u003e sensor.start()\n                LocationReadyStatus.ServiceDisabled -\u003e _uiState.showEnableGPS()\n                LocationReadyStatus.GrantDenied    -\u003e requestPermission()\n                LocationReadyStatus.BothRequired   -\u003e _uiState.showTotalFailure()\n            }\n        }\n    }\n}\n```\n\n---\n\n## ⚔️ Why Grant?\n\nMost KMP permission libraries are simple wrappers around native APIs. Grant is an **Architectural Solution**.\n\n| Feature | **Grant** | moko-permissions | accompanist-permissions |\n| :--- | :---: | :---: | :---: |\n| **No Lifecycle Binding** | ✅ | ❌ (needs BindEffect) | ❌ (needs Activity) |\n| **ViewModel Support** | **Full** | Partial | ❌ |\n| **iOS Crash Prevention** | ✅ | ❌ | ❌ |\n| **iOS Framework Isolation** | ✅ | ❌ | N/A |\n| **Android Deadlock Fix** | ✅ | ❌ | ❌ |\n| **Process Death Recovery** | **Native** | ❌ | Manual |\n| **Service Checks (GPS/BT/Health)** | ✅ | ❌ | ❌ |\n| **Android 14 Partial Access** | ✅ | Partial | ✅ |\n| **Custom Permissions** | ✅ | Limited | Limited |\n\n---\n\n## 🗺️ Platform Support \u0026 Coverage\n\n| Permission | Android | iOS | Notes |\n| :--- | :---: | :---: | :--- |\n| **Camera** | ✅ | ✅ | iOS main-thread safe + deadlock fix |\n| **Microphone** | ✅ | ✅ | Shares AVFoundation handler with Camera |\n| **Gallery (full)** | ✅ | ✅ | Android 14+ partial access (`PARTIAL_GRANTED`) |\n| **Gallery (images only)** | ✅ | ✅ | `AppGrant.GALLERY_IMAGES_ONLY` |\n| **Gallery (video only)** | ✅ | ✅ | `AppGrant.GALLERY_VIDEO_ONLY` |\n| **Storage (legacy)** | ✅ | ✅ | Pre-API 33 fallback |\n| **Location (when in use)** | ✅ | ✅ | Intelligent GPS service check included |\n| **Location (always)** | ✅ | ✅ | Android 2-step background flow handled |\n| **Notifications** | ✅ | ✅ | Android 13+ and legacy flows |\n| **Bluetooth** | ✅ | ✅ | Service status check + Scan/Connect |\n| **Bluetooth Advertise** | ✅ | ✅ | `AppGrant.BLUETOOTH_ADVERTISE` |\n| **Contacts (full)** | ✅ | ✅ | Read + Write access |\n| **Contacts (read-only)** | ✅ | ✅ | `AppGrant.READ_CONTACTS` |\n| **Calendar (full)** | ✅ | ✅ | iOS 17+ `FullAccess` / `WriteOnly` mapped correctly |\n| **Calendar (read-only)** | ✅ | ✅ | `AppGrant.READ_CALENDAR` |\n| **Motion / Activity** | ✅ | ✅ | Simulator-aware (safe mock on Simulator) |\n| **Schedule Exact Alarm** | ✅ | ✅ | Android 12+ `SCHEDULE_EXACT_ALARM` |\n\n### Service Checks (`ServiceType`)\n\n| Service | Android | iOS |\n| :--- | :---: | :---: |\n| **GPS / Location** | ✅ | ✅ |\n| **Bluetooth** | ✅ | ✅ |\n| **Wi-Fi** | ✅ | ✅ |\n| **NFC** | ✅ | — |\n| **Camera hardware** | ✅ | ✅ |\n| **Health Connect / HealthKit** | ✅ | ✅ |\n\n---\n\n## 📦 Installation\n\n```kotlin\n// shared/build.gradle.kts\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"dev.brewkits:grant-core:1.3.1\")\n            implementation(\"dev.brewkits:grant-compose:1.3.1\")    // Optional: Compose dialogs\n            implementation(\"dev.brewkits:grant-core-koin:1.3.1\")  // Optional: Koin DI support\n        }\n    }\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e 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](docs/DEPENDENCY_MANAGEMENT.md).\n\n\u003e [!NOTE]\n\u003e **Koin users**: The Koin integration was moved to `grant-core-koin` in v1.3.1. Add the new artifact alongside `grant-core` and replace `GrantPlatformModule` imports. See the [Migration Guide](docs/MIGRATION_GUIDE.md).\n\n---\n\n## 📖 Deep Dives\n\n| Guide | Description |\n| :--- | :--- |\n| [Architecture](docs/grant-core/ARCHITECTURE.md) | How concurrency, state machines, and the mutex flow work |\n| [iOS Setup](docs/platform-specific/ios/info-plist.md) | Critical `Info.plist` configuration — read before shipping |\n| [Migration Guide](docs/MIGRATION_GUIDE.md) | Upgrading from v1.2.x to v1.3.1 |\n| [Service Checking](docs/grant-core/SERVICES.md) | Combining permission + hardware service checks |\n| [Manual Injection](docs/MANUAL_INJECTION.md) | Using Grant without any DI framework |\n| [Android Reliability](docs/FIX_DEAD_CLICK_ANDROID.md) | How we fix \"Dead Clicks\" on Android |\n| [Best Practices](docs/BEST_PRACTICES.md) | Patterns for production apps |\n\n---\n\n## 🤝 Contributing\n\nWe are on a mission to make permissions a \"solved problem\" for KMP. Join us!\n\n1. Check out [CONTRIBUTING.md](CONTRIBUTING.md).\n2. Run `./gradlew :grant-core:allTests` to ensure stability.\n3. Submit your PR.\n\n---\n\n## ⚖️ License\n\nGrant is licensed under the **Apache License 2.0**. See [LICENSE](LICENSE) for details.\n\n\u003cdiv align=\"center\"\u003e\n  \u003csub\u003eBuilt with ❤️ by BrewKits\u003c/sub\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrewkits%2FGrant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrewkits%2FGrant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrewkits%2FGrant/lists"}