Android kotlin: How to request permissions at runtime

Introduction

This code demonstrates how to request permissions at runtime in an Android application written in Kotlin. The Android operating system introduced runtime permissions in Android Marshmallow (API level 23). This means apps no longer get granted all permissions they declare in the manifest file at install time. Instead, the app must explicitly request permissions from the user when it needs to access sensitive features or data on the device.

Breakdown of the Code

The code consists of three main parts:

  1. MainActivity.kt: This file contains the main activity class for the application. It defines a button that the user can click to initiate the permission request process. It also handles the result of the permission request.

  2. ManagePermissions.kt: This file defines a separate class called ManagePermissions that encapsulates the logic for checking permissions, requesting permissions, and processing the permission request result. This class promotes better code organization and reusability.

  3. Layout and Manifest Files (activity_main.xml and AndroidManifest.xml): These files define the user interface layout for the activity and declare the permissions the app needs in its manifest file.

Here's a more detailed breakdown of the functionality within these files:

  • MainActivity.kt:

    • It defines a list of permissions the app requires.
    • It initializes an instance of the ManagePermissions class to handle permission related tasks.
    • Clicking the button calls the checkPermissions function from the ManagePermissions class to initiate the permission request process.
    • The onRequestPermissionsResult method receives the result of the permission request and displays a toast message based on whether the permissions were granted or denied.
  • ManagePermissions.kt:

    • The checkPermissions function first checks if all required permissions are already granted. If not, it shows an alert dialog explaining why the permissions are needed.
    • The isPermissionsGranted function iterates through the permission list and checks if any permission is denied.
    • The deniedPermission function finds the first permission that is denied.
    • The showAlert function displays an alert dialog requesting the permissions.
    • The requestPermissions function checks if an explanation is needed before requesting permissions and then requests all permissions using ActivityCompat.requestPermissions.
    • The processPermissionsResult function processes the permission request result and returns true if all permissions are granted, false otherwise.
  • Layout and Manifest Files:

    • The activity_main.xml file defines the layout for the main activity which includes a button to trigger the permission request.
    • The AndroidManifest.xml file declares all the permissions the app needs in the <uses-permission> tags. Note that some of the declared permissions (like INTERNET) are not requested at runtime in this example.

Summary

This code provides a well-structured example of requesting permissions at runtime in an Android Kotlin application. It separates the permission handling logic into a dedicated class for better organization and reusability. The code also includes explanations for each step of the process, making it easier to understand and adapt for different permission scenarios in your own Android applications.


MainActivity.kt

package com.cfsuman.kotlinexamples

import android.Manifest
import android.content.Context
import android.os.Build
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    private val PermissionsRequestCode = 123
    private lateinit var managePermissions: ManagePermissions

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

        // Initialize a list of required permissions to request runtime
        val list = listOf<String>(
                Manifest.permission.CAMERA,
                Manifest.permission.READ_CONTACTS,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.SEND_SMS,
                Manifest.permission.READ_CALENDAR
        )

        // Initialize a new instance of ManagePermissions class
        managePermissions = ManagePermissions(this,list,PermissionsRequestCode)

        // Button to check permissions states
        button.setOnClickListener{
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                managePermissions.checkPermissions()
        }
    }


    // Receive the permissions request result
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
                                            grantResults: IntArray) {
        when (requestCode) {
            PermissionsRequestCode ->{
                val isPermissionsGranted = managePermissions
                        .processPermissionsResult(requestCode,permissions,grantResults)

                if(isPermissionsGranted){
                    // Do the task now
                    toast("Permissions granted.")
                }else{
                    toast("Permissions denied.")
                }
                return
            }
        }
    }
}


// Extension function to show toast message
fun Context.toast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
ManagePermissions.kt

package com.cfsuman.kotlinexamples

import android.app.Activity
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog


class ManagePermissions(val activity: Activity,val list: List<String>,val code:Int) {

    // Check permissions at runtime
    fun checkPermissions() {
        if (isPermissionsGranted() != PackageManager.PERMISSION_GRANTED) {
            showAlert()
        } else {
            activity.toast("Permissions already granted.")
        }
    }


    // Check permissions status
    private fun isPermissionsGranted(): Int {
        // PERMISSION_GRANTED : Constant Value: 0
        // PERMISSION_DENIED : Constant Value: -1
        var counter = 0;
        for (permission in list) {
            counter += ContextCompat.checkSelfPermission(activity, permission)
        }
        return counter
    }


    // Find the first denied permission
    private fun deniedPermission(): String {
        for (permission in list) {
            if (ContextCompat.checkSelfPermission(activity, permission)
                    == PackageManager.PERMISSION_DENIED) return permission
        }
        return ""
    }


    // Show alert dialog to request permissions
    private fun showAlert() {
        val builder = AlertDialog.Builder(activity)
        builder.setTitle("Need permission(s)")
        builder.setMessage("Some permissions are required to do the task.")
        builder.setPositiveButton("OK", { dialog, which -> requestPermissions() })
        builder.setNeutralButton("Cancel", null)
        val dialog = builder.create()
        dialog.show()
    }


    // Request the permissions at run time
    private fun requestPermissions() {
        val permission = deniedPermission()
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
            // Show an explanation asynchronously
            activity.toast("Should show an explanation.")
        } else {
            ActivityCompat.requestPermissions(activity, list.toTypedArray(), code)
        }
    }


    // Process permissions result
    fun processPermissionsResult(requestCode: Int, permissions: Array<String>,
                                 grantResults: IntArray): Boolean {
        var result = 0
        if (grantResults.isNotEmpty()) {
            for (item in grantResults) {
                result += item
            }
        }
        if (result == PackageManager.PERMISSION_GRANTED) return true
        return false
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#92a692"
    android:padding="16dp"
    android:orientation="vertical"
    >
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Request Permissions At Runtime"
        android:textAllCaps="false"
        />
</LinearLayout>
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cfsuman.kotlinexamples"
    >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.READ_CALENDAR"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>