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:
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.
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.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 theManagePermissions
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 usingActivityCompat.requestPermissions
. - The
processPermissionsResult
function processes the permission request result and returns true if all permissions are granted, false otherwise.
- The
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.
- The
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.
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()
}
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
}
}
<?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>
<?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>