{"id":31750144,"url":"https://github.com/ivamsi/easyandroidpermissions","last_synced_at":"2026-04-05T05:02:33.370Z","repository":{"id":313804855,"uuid":"1052327387","full_name":"iVamsi/EasyAndroidPermissions","owner":"iVamsi","description":"A lightweight Android library for easy runtime permission handling with Kotlin coroutines and Jetpack Compose - no more callbacks, just clean suspend functions.","archived":false,"fork":false,"pushed_at":"2025-10-02T03:46:32.000Z","size":81,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-02T05:37:43.854Z","etag":null,"topics":["actiivityresultcontracts","android","android-development","android-library","android-permissions","compose-library","coroutines","jetpack-compose","kotlin","kotlin-coroutines","lifecycle-aware","lightweight-library","permission-manager","permissions","runtime-permissions","suspend-functions","thread-safe"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iVamsi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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":"2025-09-07T21:33:05.000Z","updated_at":"2025-10-02T03:46:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"8c4a3308-87bb-4e27-a3bd-cb2be56b4ea7","html_url":"https://github.com/iVamsi/EasyAndroidPermissions","commit_stats":null,"previous_names":["ivamsi/easyandroidpermissions"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/iVamsi/EasyAndroidPermissions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FEasyAndroidPermissions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FEasyAndroidPermissions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FEasyAndroidPermissions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FEasyAndroidPermissions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iVamsi","download_url":"https://codeload.github.com/iVamsi/EasyAndroidPermissions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FEasyAndroidPermissions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279001655,"owners_count":26083147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["actiivityresultcontracts","android","android-development","android-library","android-permissions","compose-library","coroutines","jetpack-compose","kotlin","kotlin-coroutines","lifecycle-aware","lightweight-library","permission-manager","permissions","runtime-permissions","suspend-functions","thread-safe"],"created_at":"2025-10-09T15:26:43.663Z","updated_at":"2026-04-05T05:02:33.364Z","avatar_url":"https://github.com/iVamsi.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EasyAndroidPermissions 🔐\n\n[![Android Weekly](https://androidweekly.net/issues/issue-694/badge)](https://androidweekly.net/issues/issue-694)\n[![Kotlin](https://img.shields.io/badge/Kotlin-2.0.0+-purple.svg)](https://kotlinlang.org)\n[![Compose](https://img.shields.io/badge/Compose-BOM%202025.08.01+-blue.svg)](https://developer.android.com/jetpack/compose)\n[![Android](https://img.shields.io/badge/Android-API%2024+-green.svg)](https://android-arsenal.com/api?level=24)\n[![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Maven Central](https://img.shields.io/badge/Maven%20Central-2.0.0-red.svg)](https://central.sonatype.com/artifact/io.github.ivamsi/easyandroidpermissions-core/2.0.0)\n\nA lightweight Android library that bridges the gap between ActivityResultContracts permission API and Kotlin Coroutines, enabling developers to request permissions using clean, sequential suspend functions in both traditional Android components (Activities/Fragments) and Jetpack Compose applications.\n\n## Features ✨\n\n- **Coroutine-First**: Use suspend functions for permission requests\n- **Multiple Contexts**: Works with Activities, Fragments, and Compose\n- **Compose Integration**: Seamless integration with Jetpack Compose\n- **Thread-Safe**: Handle concurrent permission requests correctly\n- **Lifecycle-Aware**: Proper integration with Android lifecycle\n- **Zero Boilerplate**: No need for callback management\n- **Memory Efficient**: Optimized for performance with proper resource cleanup\n\n## Installation 📦\n\nAdd the dependencies to your `build.gradle.kts` file:\n\n```kotlin\ndependencies {\n    // Non-Compose apps: include only this line\n    implementation(\"io.github.ivamsi:easyandroidpermissions-core:2.0.0\")\n\n    // Compose apps: include this line (it already pulls in -core transitively)\n    implementation(\"io.github.ivamsi:easyandroidpermissions-compose:2.0.0\")\n}\n```\n\n**Upgrading from 1.x?** See [MIGRATION.md](./MIGRATION.md) for coordinates, API changes, and code patterns.\n\n- **Only XML / View-based UI?** Keep just the `-core` line.\n- **Compose UI?** You can add only the `-compose` line because it has an `api` dependency on `-core`, or keep both lines if you want the explicit documentation-style block.\n\n## Quick Start 🚀\n\n### Activity Usage\n\n```kotlin\nclass MainActivity : ComponentActivity() {\n    private lateinit var permissionManager: PermissionManager\n    \n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        \n        // Create permission manager\n        permissionManager = this.createPermissionManager()\n        // Or: permissionManager = PermissionManagerFactory.create(this)\n        \n        findViewById\u003cButton\u003e(R.id.cameraButton).setOnClickListener {\n            lifecycleScope.launch {\n                when (val result = permissionManager.request(Manifest.permission.CAMERA)) {\n                    PermissionResult.Granted -\u003e {\n                        // Permission granted - proceed with camera functionality\n                        openCamera()\n                    }\n                    is PermissionResult.Denied -\u003e {\n                        if (!result.canRequestAgain) {\n                            showSettingsPrompt()\n                        } else if (result.shouldShowRationale) {\n                            showPermissionEducation()\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n### Fragment Usage\n\n```kotlin\nclass CameraFragment : Fragment() {\n    private lateinit var permissionManager: PermissionManager\n    \n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        \n        // Create permission manager\n        permissionManager = this.createPermissionManager()\n        // Or: permissionManager = PermissionManagerFactory.create(this)\n    }\n    \n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        \n        binding.recordButton.setOnClickListener {\n            viewLifecycleOwner.lifecycleScope.launch {\n                val permissions = listOf(\n                    Manifest.permission.CAMERA,\n                    Manifest.permission.RECORD_AUDIO\n                )\n                \n                val results = permissionManager.requestMultiple(permissions)\n                val denied = results.filterValues { !it.isGranted }\n                \n                if (denied.isEmpty()) {\n                    startRecording()\n                } else {\n                    handleDeniedPermissions(denied.keys)\n                }\n            }\n        }\n    }\n}\n```\n\n### Compose Usage\n\n```kotlin\n@Composable\nfun CameraScreen() {\n    val permissionManager = rememberPermissionManager()\n    val scope = rememberCoroutineScope()\n    \n    Button(\n        onClick = {\n            scope.launch {\n                when (val result = permissionManager.request(Manifest.permission.CAMERA)) {\n                    PermissionResult.Granted -\u003e openCamera()\n                    is PermissionResult.Denied -\u003e {\n                        if (!result.canRequestAgain) {\n                            showSettingsPrompt()\n                        } else {\n                            showPermissionDeniedMessage()\n                        }\n                    }\n                }\n            }\n        }\n    ) {\n        Text(\"Open Camera\")\n    }\n}\n```\n\n### Multiple Permissions (Works in all contexts)\n\n```kotlin\n// In Activity, Fragment, or Compose - same API!\nval permissions = listOf(\n    Manifest.permission.CAMERA,\n    Manifest.permission.RECORD_AUDIO,\n    Manifest.permission.WRITE_EXTERNAL_STORAGE\n)\n\nval results = permissionManager.requestMultiple(permissions)\nval allGranted = results.values.all { it.isGranted }\n\nif (allGranted) {\n    // All permissions granted\n    startMediaRecording()\n} else {\n    // Handle denied permissions\n    val denied = results.filterValues { !it.isGranted }.keys\n    handleDeniedPermissions(denied)\n}\n```\n\n### Check Permission Status (Works in all contexts)\n\n```kotlin\n// Check single permission\nval cameraState = permissionManager.getPermissionState(Manifest.permission.CAMERA)\nif (cameraState.isGranted) {\n    startCamera()\n} else if (cameraState is PermissionResult.Denied \u0026\u0026 !cameraState.canRequestAgain) {\n    showSettingsPrompt()\n}\n\n// Check multiple permissions\nval permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)\nval permissionStatus = permissionManager.getPermissionStates(permissions)\nval denied = permissionStatus.filterValues { !it.isGranted }.keys\n\n// Observe tracked states (Compose example)\nval trackedStates by permissionManager.permissionStates.collectAsState()\n```\n\n`permissionStates` is a cold `StateFlow` that emits whenever EasyAndroidPermissions learns about a new permission state (e.g., after a request or an explicit `getPermissionState()` call). It plugs directly into Compose via `collectAsState()` or into View-based UIs via `lifecycleScope.launch { permissionStates.collect { … } }`.\n\n## API Reference 📚\n\n### PermissionManager Interface\n\n```kotlin\ninterface PermissionManager {\n    val permissionStates: StateFlow\u003cMap\u003cString, PermissionResult\u003e\u003e\n\n    @MainThread\n    @CheckResult\n    suspend fun request(permission: String): PermissionResult\n\n    @MainThread\n    @CheckResult\n    suspend fun requestMultiple(permissions: List\u003cString\u003e): Map\u003cString, PermissionResult\u003e\n\n    fun getPermissionState(permission: String): PermissionResult\n    fun getPermissionStates(permissions: List\u003cString\u003e): Map\u003cString, PermissionResult\u003e\n\n    fun shouldShowRationale(permission: String): Boolean\n    fun canRequestAgain(permission: String): Boolean\n}\n```\n\n### PermissionResult\n\n```kotlin\nsealed interface PermissionResult {\n    data object Granted : PermissionResult\n    data class Denied(\n        val canRequestAgain: Boolean,\n        val shouldShowRationale: Boolean\n    ) : PermissionResult\n}\n```\n\n### Factory Methods\n\n```kotlin\n// Extension functions for easy creation\nfun ComponentActivity.createPermissionManager(): PermissionManager\nfun Fragment.createPermissionManager(): PermissionManager\n\n// Factory methods\nPermissionManagerFactory.create(activity: ComponentActivity): PermissionManager\nPermissionManagerFactory.create(fragment: Fragment): PermissionManager\nPermissionManagerFactory.create(\n    lifecycleOwner: LifecycleOwner,\n    caller: ActivityResultCaller,\n    contextProvider: () -\u003e Context?,\n    rationaleProvider: (String) -\u003e Boolean = { false }\n): PermissionManager\n```\n\n### Composable Functions\n\n```kotlin\n/**\n * Creates and remembers a PermissionManager instance.\n * Must be called within a Composable context.\n */\n@Composable\nfun rememberPermissionManager(): PermissionManager\n```\n\n## Key Benefits 🌟\n\n### Before (Traditional Approach)\n```kotlin\nclass MainActivity : ComponentActivity() {\n    private val requestPermissionLauncher = \n        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -\u003e\n            if (isGranted) {\n                // Permission granted\n            } else {\n                // Permission denied\n            }\n        }\n    \n    private fun requestCameraPermission() {\n        when {\n            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == \n                PackageManager.PERMISSION_GRANTED -\u003e {\n                // Permission already granted\n            }\n            shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -\u003e {\n                // Show rationale\n            }\n            else -\u003e {\n                requestPermissionLauncher.launch(Manifest.permission.CAMERA)\n            }\n        }\n    }\n}\n```\n\n### After (With EasyAndroidPermissions)\n```kotlin\n// Works the same in Activity, Fragment, or Compose!\nlifecycleScope.launch { // or viewLifecycleOwner.lifecycleScope in Fragment\n    when (permissionManager.request(Manifest.permission.CAMERA)) {\n        PermissionResult.Granted -\u003e { /* proceed */ }\n        is PermissionResult.Denied -\u003e { /* explain or open settings */ }\n    }\n}\n```\n\n## Advanced Usage 🔧\n\n### Error Handling\n\n```kotlin\nscope.launch {\n    try {\n        when (val result = permissionManager.request(Manifest.permission.CAMERA)) {\n            PermissionResult.Granted -\u003e openCamera()\n            is PermissionResult.Denied -\u003e showPermissionEducation()\n        }\n    } catch (e: Exception) {\n        // Handle any unexpected errors\n        Log.e(\"Permission\", \"Error requesting permission\", e)\n    }\n}\n```\n\n### Conditional Permission Requests\n\n```kotlin\nscope.launch {\n    // Only request if not already granted\n    if (!permissionManager.getPermissionState(Manifest.permission.LOCATION).isGranted) {\n        when (permissionManager.request(Manifest.permission.LOCATION)) {\n            PermissionResult.Granted -\u003e startLocationUpdates()\n            is PermissionResult.Denied -\u003e { /* handle */ }\n        }\n    } else {\n        // Already granted\n        startLocationUpdates()\n    }\n}\n```\n\n## Best Practices 📋\n\n1. **Always check permissions before requesting**: The library optimizes by checking current status first, but explicit checks make your intent clear.\n\n2. **Handle permission denials gracefully**: Provide alternative functionality or clear explanations when permissions are denied.\n\n3. **Request permissions contextually**: Request permissions when the user initiates an action that requires them, not upfront.\n\n4. **Use the minimal required permissions**: Only request permissions your app actually needs.\n\n### Android 14 \u0026 15 Updates\n\n- Android 13+ introduces `NEARBY_WIFI_DEVICES`; Android 14 adds background sensor gating via `BODY_SENSORS_BACKGROUND`. Declare these permissions (see the demo manifest) and request them only on supported API levels.\n- When `PermissionResult.Denied.canRequestAgain` is `false`, Google expects you to route the user to Settings using `Context.createPermissionSettingsIntent()`. Follow the [official guidance](https://developer.android.com/training/permissions/requesting).\n- Android 15 special cases (e.g., `SCHEDULE_EXACT_ALARM`, background sensors) sometimes require additional UX or policy disclosures. Review the [Android 15 behavior changes](https://developer.android.com/about/versions/15/behavior-changes-all#runtime-permissions) before shipping.\n\n## Requirements 📋\n\n- **Minimum SDK**: API 24 (Android 7.0)\n- **Kotlin**: 2.0.0 or higher\n- **Jetpack Compose**: BOM 2025.08.01 or higher\n- **Coroutines**: 1.7.0 or higher\n\n## Sample App 📱\n\nCheck out the [EasyAndroidPermissionsDemo](./EasyAndroidPermissionsDemo) module for a complete sample application demonstrating various use cases:\n\n- **Traditional Activity Demo**: XML layouts with the lifecycle-aware permission manager\n- **Fragment Demo**: XML layouts with the same lifecycle-aware APIs\n- **Individual Permission Requests**: Camera, microphone, location permissions\n- **Multiple Permission Requests**: Request multiple permissions at once\n- **Permission Status Tracking**: Real-time status display\n- **Proper Lifecycle Integration**: Activity and Fragment lifecycle handling\n\n## Contributing 🤝\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n## License 📄\n\n```\nCopyright 2025 Vamsi Vaddavalli\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\n---\n\n**EasyAndroidPermissions** - *Making Android permissions simple, clean, and coroutine-friendly* 🚀","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivamsi%2Feasyandroidpermissions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivamsi%2Feasyandroidpermissions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivamsi%2Feasyandroidpermissions/lists"}