EasyAndroidPermissions
A lightweight Android library for easy runtime permission handling with Kotlin coroutines and Jetpack Compose - no more callbacks, just clean suspend functions.
Install / Use
/learn @iVamsi/EasyAndroidPermissionsREADME
EasyAndroidPermissions 🔐
A 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.
Features ✨
- Coroutine-First: Use suspend functions for permission requests
- Multiple Contexts: Works with Activities, Fragments, and Compose
- Compose Integration: Seamless integration with Jetpack Compose
- Thread-Safe: Handle concurrent permission requests correctly
- Lifecycle-Aware: Proper integration with Android lifecycle
- Zero Boilerplate: No need for callback management
- Memory Efficient: Optimized for performance with proper resource cleanup
Installation 📦
Add the dependency to your build.gradle.kts file:
dependencies {
implementation("io.github.ivamsi:easyandroidpermissions:1.0.2")
}
Quick Start 🚀
Activity Usage
class MainActivity : ComponentActivity() {
private lateinit var permissionManager: PermissionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create permission manager
permissionManager = this.createPermissionManager()
// Or: permissionManager = PermissionManagerFactory.create(this)
findViewById<Button>(R.id.cameraButton).setOnClickListener {
lifecycleScope.launch {
val isGranted = permissionManager.request(Manifest.permission.CAMERA)
if (isGranted) {
// Permission granted - proceed with camera functionality
openCamera()
} else {
// Permission denied - show user feedback
showPermissionDeniedMessage()
}
}
}
}
}
Fragment Usage
class CameraFragment : Fragment() {
private lateinit var permissionManager: PermissionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create permission manager
permissionManager = this.createPermissionManager()
// Or: permissionManager = PermissionManagerFactory.create(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recordButton.setOnClickListener {
viewLifecycleOwner.lifecycleScope.launch {
val permissions = listOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
val results = permissionManager.requestMultiple(permissions)
val allGranted = results.values.all { it }
if (allGranted) {
startRecording()
} else {
val denied = results.filterValues { !it }.keys
handleDeniedPermissions(denied)
}
}
}
}
}
Compose Usage
@Composable
fun CameraScreen() {
val permissionManager = rememberPermissionManager()
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
val isGranted = permissionManager.request(Manifest.permission.CAMERA)
if (isGranted) {
// Permission granted - proceed with camera functionality
openCamera()
} else {
// Permission denied - show user feedback
showPermissionDeniedMessage()
}
}
}
) {
Text("Open Camera")
}
}
Multiple Permissions (Works in all contexts)
// In Activity, Fragment, or Compose - same API!
val permissions = listOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
val results = permissionManager.requestMultiple(permissions)
val allGranted = results.values.all { it }
if (allGranted) {
// All permissions granted
startMediaRecording()
} else {
// Handle denied permissions
val denied = results.filterValues { !it }.keys
handleDeniedPermissions(denied)
}
Check Permission Status (Works in all contexts)
// Check single permission
val hasCameraPermission = permissionManager.isPermissionGranted(Manifest.permission.CAMERA)
// Check multiple permissions
val permissions = listOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
val permissionStatus = permissionManager.arePermissionsGranted(permissions)
// Use the results as needed
if (hasCameraPermission) {
// Camera is available
} else {
// Request camera permission
}
API Reference 📚
PermissionManager Interface
interface PermissionManager {
/**
* Requests a single runtime permission.
* @param permission The permission to request
* @return true if granted, false if denied
*/
suspend fun request(permission: String): Boolean
/**
* Requests multiple runtime permissions.
* @param permissions List of permissions to request
* @return Map of permissions to their granted status
*/
suspend fun requestMultiple(permissions: List<String>): Map<String, Boolean>
/**
* Checks if a permission is currently granted.
* @param permission The permission to check
* @return true if granted, false otherwise
*/
fun isPermissionGranted(permission: String): Boolean
/**
* Checks multiple permissions' granted status.
* @param permissions List of permissions to check
* @return Map of permissions to their granted status
*/
fun arePermissionsGranted(permissions: List<String>): Map<String, Boolean>
}
Factory Methods
// Extension functions for easy creation
fun ComponentActivity.createPermissionManager(): PermissionManager
fun Fragment.createPermissionManager(): PermissionManager
// Factory methods
PermissionManagerFactory.create(activity: ComponentActivity): PermissionManager
PermissionManagerFactory.create(fragment: Fragment): PermissionManager
Composable Functions
/**
* Creates and remembers a PermissionManager instance.
* Must be called within a Composable context.
*/
@Composable
fun rememberPermissionManager(): PermissionManager
Key Benefits 🌟
Before (Traditional Approach)
class MainActivity : ComponentActivity() {
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
// Permission granted
} else {
// Permission denied
}
}
private fun requestCameraPermission() {
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED -> {
// Permission already granted
}
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
// Show rationale
}
else -> {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
}
After (With EasyAndroidPermissions)
// Works the same in Activity, Fragment, or Compose!
lifecycleScope.launch { // or viewLifecycleOwner.lifecycleScope in Fragment
if (permissionManager.request(Manifest.permission.CAMERA)) {
// Permission granted
} else {
// Permission denied
}
}
Advanced Usage 🔧
Error Handling
scope.launch {
try {
val isGranted = permissionManager.request(Manifest.permission.CAMERA)
if (isGranted) {
openCamera()
} else {
showPermissionEducation()
}
} catch (e: Exception) {
// Handle any unexpected errors
Log.e("Permission", "Error requesting permission", e)
}
}
Conditional Permission Requests
scope.launch {
// Only request if not already granted
if (!permissionManager.isPermissionGranted(Manifest.permission.LOCATION)) {
val isGranted = permissionManager.request(Manifest.permission.LOCATION)
if (isGranted) {
startLocationUpdates()
}
} else {
// Already granted
startLocationUpdates()
}
}
Best Practices 📋
-
Always check permissions before requesting: The library optimizes by checking current status first, but explicit checks make your intent clear.
-
Handle permission denials gracefully: Provide alternative functionality or clear explanations when permissions are denied.
-
Request permissions contextually: Request permissions when the user initiates an action that requires them, not upfront.
-
Use the minimal required permissions: Only request permissions your app act
