SkillAgentSearch skills...

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/EasyAndroidPermissions

README

EasyAndroidPermissions 🔐

Android Weekly Kotlin Compose Android License Maven Central

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 📋

  1. Always check permissions before requesting: The library optimizes by checking current status first, but explicit checks make your intent clear.

  2. Handle permission denials gracefully: Provide alternative functionality or clear explanations when permissions are denied.

  3. Request permissions contextually: Request permissions when the user initiates an action that requires them, not upfront.

  4. Use the minimal required permissions: Only request permissions your app act

View on GitHub
GitHub Stars27
CategoryDevelopment
Updated4mo ago
Forks0

Languages

Kotlin

Security Score

77/100

Audited on Nov 23, 2025

No findings