# Requesting Multiple Runtime Permission Made EASY

> Requesting runtime permission in android kotlin with minimum boiler plate code and

Source: https://krishparekhdev.com/blog/requesting-multiple-runtime-permission-made-easy
Published: 2022-06-10
Tags: android, app-development, android-app-development, kotlin

---

As a developer dealing with [permission](https://developer.android.com/guide/topics/permissions/overview) in android is time-consuming, writing too much boilerplate code.
Recently in [Android 11](https://developer.android.com/about/versions/11/privacy/permissions), users can give permission “only once” or “while using the app,” They can revoke the consent if the app is not in use for a long time.

But today, we will learn a new way to request multiple runtime permission with minimal boilerplate code.

Step 1: First, we need to declare our permission in the [manifest file](https://developer.android.com/training/permissions/declaring)
before requesting

```
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
```
Here we will access permission to read contacts, access location and storage.

Step 2: Inside our activity, we need to declare our `ActivityResultLauncher<I>` and variable which keeps track of our permission

```kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
    private var isContactPermissionGranted = false
    private var isLocationPermissionGranted = false
    private var isStoragePermissionGranted = false
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}
```
We use [`ActivityResultLauncher<I>`](https://developer.android.com/reference/androidx/activity/result/ActivityResultLauncher) to launch the other activity where **I** is the type of input required to launch.

Step 3: We need to initialize our permissionLauncher

```kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
    private var isContactPermissionGranted = false
    private var isLocationPermissionGranted = false
    private var isStoragePermissionGranted = false
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

         permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
                isContactPermissionGranted = permissions[Manifest.permission.READ_CONTACTS] ?: isContactPermissionGranted
                isLocationPermissionGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: isLocationPermissionGranted
                isStoragePermissionGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: isStoragePermissionGranted
            }

    }
}

```

The ActivityResult API provides [registerForActivityResult](https://developer.android.com/reference/androidx/activity/result/ActivityResultCaller#summary) API for registering the result callback. In simple terms, it will register the output which we will receive from the permission dialog (if the user has granted permission or denied it)

registerForActivityResult() takes an [**ActivityResultContract**](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract#summary) and an [**ActivityResultCallback**](https://developer.android.com/reference/androidx/activity/result/ActivityResultCallback#summary) and returns an [**ActivityResultLauncher**](https://developer.android.com/reference/androidx/activity/result/ActivityResultLauncher#summary) which you’ll use to launch the other activity.

What is **ActivityResultContract**?<br />
A contract specifying that an activity can be called with an input of type I and produce an output of type O.

What is **ActivityResultCallback**? <br />
It is a single method interface with an [onActivityResult()](https://developer.android.com/reference/androidx/activity/result/ActivityResultCallback#summary) method that takes an object of the output type defined in the ActivityResultContract. When the output is available onActivityResult() is executed in simple terms.

As we have to request multiple permission for users, we need to define a contract that can handle multiple runtime permission that we have. Here we are using [ActivityResultContracts.RequestMultiplePermissions.](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.RequestMultiplePermissions#summary)

Let's dive deeper and check how this RequestMultiplePermissions() works

```java
public static final class RequestMultiplePermissions
            extends ActivityResultContract<String[], java.util.Map<String, Boolean>> {
              ..............
    }
```
RequestMultiplePermissions() is a static final class that extends `ActivityResultContract<I,O>` for input; we need to pass an array of strings. We get output as Map where the key is of String type, and the value is of Boolean type.

Now let's see how this class generates the output

RequestMultiplePermissions Implements a method [createIntent()](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.RequestMultiplePermissions#createIntent(android.content.Context,kotlin.Array)) which will create that permission dialog and request permission.

When the result is obtained from the user inside RequestMultiplePermissions, we have a method [parseResult()](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.RequestMultiplePermissions#parseResult(kotlin.Int,android.content.Intent)) which will convert the result obtained from onActivityResult to desired output.

Let's look inside the code for parse result here as we can see it creates Map With permission name and a boolean which is checking if permission is granted, and then we return that result.

So inside our ActivityResultCallback() \{ of registerForActivityResult() as described above \} we will receive a Map as output.

```java
public static final class RequestMultiplePermissions
            extends ActivityResultContract<String[], java.util.Map<String, Boolean>> {

        @NonNull
        @Override
        public Map<String, Boolean> parseResult(int resultCode,
                @Nullable Intent intent) {
            if (resultCode != Activity.RESULT_OK) return emptyMap();
            if (intent == null) return emptyMap();

            String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS);
            int[] grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS);
            if (grantResults == null || permissions == null) return emptyMap();

            Map<String, Boolean> result = new HashMap<>();
            for (int i = 0, size = permissions.length; i < size; i++) {
                result.put(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED);
            }
            return result;
        }

        @NonNull
        static Intent createIntent(@NonNull String[] input) {
            return new Intent(ACTION_REQUEST_PERMISSIONS).putExtra(EXTRA_PERMISSIONS, input);
        }
    }
```

While registerForActivityResult() registers your callback, it does not launch the other activity and kick off the request for a result. Instead, this is the responsibility of the returned ActivityResultLauncher instance. (our permissionLauncher variable holds the instance of ActivityResultLauncher)

In simple term
registerForActivityResult() keeps track of our permissions that are granted or denied.

ActivityResultlauncher is used to launch other activities. (permission dialog in this case)

Step 4: Request Permission

```kotlin
class MainActivity : AppCompatActivity() {

    private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
    private var isContactPermissionGranted = false
    private var isLocationPermissionGranted = false
    private var isStoragePermissionGranted = false
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
                Log.d(TAG, "permissions :$permissions")
                isContactPermissionGranted = permissions[Manifest.permission.READ_CONTACTS] ?: isContactPermissionGranted
                isLocationPermissionGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: isLocationPermissionGranted
                isStoragePermissionGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: isStoragePermissionGranted
            }

        requestPermissions()

    }

    private fun requestPermissions() {
        isContactPermissionGranted = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.READ_CONTACTS
        ) == PackageManager.PERMISSION_GRANTED

        isLocationPermissionGranted = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED

        isStoragePermissionGranted = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED

        val permissionRequestList = ArrayList<String>()

        if (!isContactPermissionGranted) {
            permissionRequestList.add(Manifest.permission.READ_CONTACTS)
        }

        if (!isLocationPermissionGranted) {
            permissionRequestList.add(Manifest.permission.ACCESS_FINE_LOCATION)
        }

        if (!isStoragePermissionGranted) {
            permissionRequestList.add(Manifest.permission.READ_EXTERNAL_STORAGE)
        }

        if (permissionRequestList.isNotEmpty()) {
            permissionLauncher.launch(permissionRequestList.toTypedArray())
        }
    }
}
```
Here in our requestForPermission() method, we first check whether all the permission is granted or denied.

I have declared a permissionRequestList variable that holds a list of all the permission not granted.

Then we check if permissionRequestList is not empty, which means the user has not given access to the permissions.

So Then we launch!!

What [**Launch**](https://developer.android.com/reference/androidx/activity/result/ActivityResultLauncher#launch(I)) Does? <br />
If input exists, the launcher takes the input that matches the type of the ActivityResultContract Here input type is an array of strings because of ActivityResultContract.RequestMultiplePermission() takes an array of strings as input, as discussed earlier.

Calling launch() starts the process of producing the result. When the user is done with the subsequent activity and returns, the onActivityResult() from the ActivityResultCallback is executed.

So here we receive a `Map<String, Boolean>` which we get from parseResult() method as discussed earlier and check if the permission is granted and set their variables accordingly.

```kotlin
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
                isContactPermissionGranted = permissions[Manifest.permission.READ_CONTACTS] ?: isContactPermissionGranted
                isLocationPermissionGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: isLocationPermissionGranted
                isStoragePermissionGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: isStoragePermissionGranted
            }
```
