Kotlin & XML: Handling Permission Requests in Android UI Actions

Handling permissions in Android is a crucial part of developing applications, especially when those permissions are tied directly to UI actions initiated by the user. With the shift towards more granular permission control, it’s important to manage these requests efficiently, providing users with clear explanations and seamless access to necessary features. This post will delve into how to handle permission requests linked to UI actions in Kotlin while using XML layouts for Android development.

Understanding Android Permissions

Android permissions are restrictions placed on applications that limit access to certain functions of the system or device. These permissions are designed to protect user privacy and data. Starting from Android 6.0 (API level 23), the permission model requires applications to request certain permissions at runtime. This means you need to ask the user for permission when it’s actually needed, not just at install time.

Why Handle Permissions with UI Actions?

Requesting permissions right before a UI action requires it enhances user experience in several ways:

  • Contextual Relevance: The user understands exactly why the permission is needed because it’s directly related to their action.
  • Increased Trust: Requesting permissions in context reduces user frustration and improves trust in your app.
  • Reduced Permission Fatigue: Users are less likely to grant permissions upfront that may not seem immediately relevant.

Steps to Handle Permissions with UI Actions

Step 1: Add Dependencies

First, ensure you have the necessary dependencies. You generally don’t need additional dependencies just for permission handling, as the core functionalities are provided by the Android SDK itself.

Step 2: Declare Permissions in the Manifest

Before you can request permissions at runtime, you must declare them in your AndroidManifest.xml file.



    
    

    
        
            
                
                
            
        
    


Step 3: Implement Permission Request Logic in Kotlin

Here’s how you can implement the permission request logic in your Activity or Fragment using Kotlin. This example focuses on requesting camera permission before opening the camera.


import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {

    private val CAMERA_PERMISSION_CODE = 101
    private lateinit var openCameraButton: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        openCameraButton = findViewById(R.id.openCameraButton)
        openCameraButton.setOnClickListener {
            checkCameraPermissionAndOpenCamera()
        }
    }

    private fun checkCameraPermissionAndOpenCamera() {
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            // Permission already granted, open the camera
            openCamera()
        } else {
            // Permission not granted, request it
            requestCameraPermission()
        }
    }

    private fun requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(
                this,
                Manifest.permission.CAMERA
            )
        ) {
            // Show an explanation to the user *asynchronously* -- don't block
            // this thread waiting for the user's response! After the user
            // sees the explanation, try again to request the permission.
            AlertDialog.Builder(this)
                .setTitle("Camera Permission Required")
                .setMessage("This app needs the camera to take photos. Please grant camera permission.")
                .setPositiveButton("OK") { _, _ ->
                    ActivityCompat.requestPermissions(
                        this@MainActivity,
                        arrayOf(Manifest.permission.CAMERA),
                        CAMERA_PERMISSION_CODE
                    )
                }
                .setNegativeButton("Cancel", null)
                .show()
        } else {
            // No explanation needed, we can request the permission.
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.CAMERA),
                CAMERA_PERMISSION_CODE
            )
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        when (requestCode) {
            CAMERA_PERMISSION_CODE -> {
                if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                    // Permission was granted, open the camera
                    openCamera()
                } else {
                    // Permission denied, disable the functionality that depends on this permission.
                    // Optionally, inform the user that the feature is disabled because they denied the permission.
                    Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show()

                    // Check if the user has denied permission permanently
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
                        // Navigate user to app settings
                        showPermissionDeniedPermanentlyDialog()
                    }
                }
                return
            }
            else -> {
                // Ignore all other requests.
            }
        }
    }

    private fun openCamera() {
        // Intent to open the camera
        val cameraIntent = Intent("android.media.action.IMAGE_CAPTURE")
        startActivity(cameraIntent)
    }

    private fun showPermissionDeniedPermanentlyDialog() {
        AlertDialog.Builder(this)
            .setTitle("Camera Permission Denied")
            .setMessage("Camera permission is required for this feature. Please enable it in app settings.")
            .setPositiveButton("Go to Settings") { _, _ ->
                // Open application settings
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                val uri = Uri.fromParts("package", packageName, null)
                intent.data = uri
                startActivity(intent)
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

Step 4: Explanation of the Code

  • Permission Check:
    • ContextCompat.checkSelfPermission() is used to check if the app already has the specified permission.
  • Request Permission:
    • If permission is not granted, ActivityCompat.requestPermissions() is called to request the permission from the user.
  • Handle Permission Result:
    • The onRequestPermissionsResult() method is overridden to handle the result of the permission request.
    • Check if the permission was granted, and take appropriate action (e.g., open the camera, show a message).
  • Show Rationale:
    • ActivityCompat.shouldShowRequestPermissionRationale() should be used to display a rationale for why the app needs the permission before actually requesting it. This ensures that users understand why the app is asking for permission.
  • Handling Permanently Denied Permissions:
    • If a user denies a permission request and selects “Don’t ask again”, the app will not be able to request the permission again using the standard requestPermissions flow. In this scenario, ActivityCompat.shouldShowRequestPermissionRationale() will return false. You need to guide the user to the settings so that they can enable the permission.

Step 5: Implement UI (XML Layout)

Here is the XML layout (activity_main.xml) containing a button to trigger the camera.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/openCameraButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Camera"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Best Practices for Permission Requests

  • Explain Why: Always provide a clear explanation of why your app needs the permission before requesting it. Use the shouldShowRequestPermissionRationale to show dialog.
  • Request in Context: Request permissions only when the specific feature that requires them is being used.
  • Handle Denials Gracefully: If the user denies a permission, gracefully degrade the app’s functionality and inform the user of the limitations.
  • Consider Alternatives: Evaluate whether there are alternative ways to achieve the desired functionality without requiring the permission.
  • Test Thoroughly: Test your permission handling logic on different Android versions and devices to ensure it works as expected.
  • Use Libraries: For complex permission handling scenarios, consider using libraries like EasyPermissions to simplify the process.

Conclusion

Handling permissions effectively ensures user trust and application robustness. By implementing permission requests in response to UI actions, developers can provide a more intuitive and context-aware experience. With Kotlin and XML, Android apps can manage permissions efficiently, adhering to best practices and enhancing the overall user journey.