Android Compose Requesting Multiple Permissions with Custom Message

You are currently viewing Android Compose Requesting Multiple Permissions with Custom Message

This post will be a continuation from the previous post about requesting permissions in Android Compose.

Android Compose Streamline Requesting Permissions With Example

Here I wanted to create a separate function to be able to request multiple permissions. The flow of the method uses index counting on the Lifecycles of the application.

This time I created a class to hold each permission object so I can create custom messages for each if they were denied access. The messages will be displayed on an AlertDialog.

The function will take a list of the permission objects and iterate through the list. The callback will update the UI on the MainActivity.

I created a package off the main named permissions to hold all my permission classes. Just trying to keep things neat.

Ex. com.example.your_package_name.permissions

Inside I created a class named RequestingPermission. This will hold the permission reference object for each item in list.

class RequestingPermission(
    var isGranted : Boolean = false,
    val permissionName : String = "",
    val message : String = "",
    var showRational : Boolean = false
)

Next I created the MyPermissionState in the permission package. This will be what does the work for the permission. It will check the permissions and return the result back to the MainActivity through a callback.

@Composable
fun MyPermissionState(
    permissionState: List<RequestingPermission>,
    permissionChanged: (Int, Boolean) -> Unit,
    rationalChanged: (Int, Boolean) -> Unit
) {

    val context = LocalContext.current

    var onResumeIndex by remember { mutableStateOf(-1) }

    val requestPermissions =
        rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
            permissions.forEach { (name, state) ->
                when (name) {
                    permissionState[onResumeIndex].permissionName -> {
                        permissionChanged(onResumeIndex, state)
                        onResumeIndex++
                    }
                }
            }
        }

    val goToSettingsRequest =
        rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->

        }

    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(key1 = lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_RESUME) {
                onResumeIndex = 0
                val isGranted = context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED
                if(isGranted){
                    rationalChanged(onResumeIndex, false)
                    permissionChanged(onResumeIndex, isGranted)
                    onResumeIndex++
                }
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    LaunchedEffect(onResumeIndex) {
        if (onResumeIndex < permissionState.size) {
            if(!permissionState[onResumeIndex].isGranted) {
                val isGranted =
                    context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED
                permissionChanged(onResumeIndex, isGranted)
                if (!isGranted) {
                    val showRationaleState = ActivityCompat.shouldShowRequestPermissionRationale(
                        context as Activity,
                        permissionState[onResumeIndex].permissionName
                    )
                    rationalChanged(onResumeIndex, showRationaleState)
                    if(!showRationaleState){
                        requestPermissions.launch(arrayOf(permissionState[onResumeIndex].permissionName))
                    }
                } else {
                    rationalChanged(onResumeIndex, false)
                }
            } else {
                rationalChanged(onResumeIndex, false)
            }
        }
    }

    if (onResumeIndex in 0 .. permissionState.size-1 && permissionState[onResumeIndex].showRational) {
        AlertDialog(
            onDismissRequest = {
                val isGranted =
                    context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED
                permissionChanged(onResumeIndex, isGranted)
                rationalChanged(onResumeIndex, false)
                onResumeIndex++
            },
            text = {
                Text(text = permissionState[onResumeIndex].message)
            },
            confirmButton = {
                TextButton(onClick = {
                    goToSettingsRequest.launch(
                        Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
                            Uri.fromParts("package", context.packageName, null)
                        ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    )

                }) {
                    Text(text = "Open App Settings")
                }
            }
        )
    }
}

Now for the MainActivity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            SamplePermissionsTheme {
                val permissionState = remember {
                    mutableStateListOf(
                        RequestingPermission(
                            false,
                            permissionName = Manifest.permission.POST_NOTIFICATIONS,
                            message = "Please approve permission in order to receive notifications."
                        ),
                        RequestingPermission(
                            false,
                            permissionName = Manifest.permission.BLUETOOTH_CONNECT,
                            message = "Please approve permission in order to use bluetooth."
                        )
                    )

                }

                Scaffold(modifier = Modifier.fillMaxSize(), topBar = {
                }) { innerPadding ->
                    Row(
                        modifier = Modifier.padding(20.dp),
                        horizontalArrangement = Arrangement.Center
                    ) {
                        MyPermissionState(
                            permissionState,
                            permissionChanged = { index, isGranted ->
                                val sample = permissionState[index]
                                sample.isGranted = isGranted
                                permissionState.removeAt(index)
                                permissionState.add(index, sample)
                            },
                            rationalChanged = { index, showRational ->
                                val sample = permissionState[index]
                                sample.showRational = showRational
                                permissionState.removeAt(index)
                                permissionState.add(index, sample)
                            }
                        )
                    }
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(innerPadding)
                            .background(Color.Black),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        permissionState.forEach { requestingPermission ->
                            Text(
                                text = "${requestingPermission.permissionName}\nMessage = \n${if (requestingPermission.isGranted) "Permission Granted" else "Permission Denied"}",
                                color = if (requestingPermission.isGranted) Color.Green else Color.Red
                            )
                        }
                    }

                }
            }
        }
    }
}

I randomly chose two different permissions

-POST_NOTIFICATIONS

-BLUETOOTH_CONNECT

You could choose any you want, this was just for testing.

Now update the AndroidManifest.xml to add the permissions

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

NOTE

I have done this in a project before and it worked fine but the code was project specific. I wanted to post a function that could be universally used for almost all project scenarios that needed minimal adjustment.

While doing this, I was encountering a UID error when the app goes into the settings. My previous project had the same code without issues.

The idea was to validate the result from the ON_RESUME method and update the UI based on that from the user making a change in the app settings.

The code pasted here works fine, just a fyi. If anyone sees a potential reason for the error, please let me know.

OLD STEPPING STONES

Below is how I starting building the function above. The one above is the best approach with less code.

I just posted this below to show how my mindset was from start to finish.

I wanted to take out the ON_CREATE call and finalize it using only the ON_RESUME call. ON_RESUME will get called after ON_CREATE anyway so no need to create a check in ON_CREATE.

@Composable
fun MyPermissionState(
    permissionState: List<RequestingPermission>,
    permissionChanged: (Int, Boolean) -> Unit,
    rationalChanged: (Int, Boolean) -> Unit
) {

    val context = LocalContext.current

    var currentIndex by remember { mutableStateOf(-1) }
    var onResumeIndex by remember { mutableStateOf(-1) }

    val requestPermissions =
        rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
            permissions.forEach { (name, state) ->
                when (name) {
                    permissionState[currentIndex].permissionName -> {
                        permissionChanged(currentIndex, state)
                        currentIndex++
                    }
                }
            }
        }

    val goToSettingsRequest =
        rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->

        }

    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(key1 = lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_CREATE) {
                currentIndex = 0
            }
            if (event == Lifecycle.Event.ON_RESUME) {
                onResumeIndex = 0
                if(context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED){
                    rationalChanged(onResumeIndex, false)
                    permissionChanged(onResumeIndex, true)
                    onResumeIndex++
                }
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    LaunchedEffect(onResumeIndex) {
        if (onResumeIndex < permissionState.size) {
            val isGranted =
                context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED
            permissionChanged(onResumeIndex, isGranted)
            if(!isGranted){
                val showRationaleState = ActivityCompat.shouldShowRequestPermissionRationale(
                    context as Activity,
                    permissionState[onResumeIndex].permissionName
                )
                rationalChanged(onResumeIndex, showRationaleState)
            } else {
                rationalChanged(onResumeIndex, false)
            }

            if(!permissionState[onResumeIndex].showRational) {
                onResumeIndex++
            }
        }
    }


    LaunchedEffect(currentIndex) {
        if (currentIndex < permissionState.size) {
            val isGranted =
                context.checkSelfPermission(permissionState[currentIndex].permissionName) == PackageManager.PERMISSION_GRANTED
            when {
                isGranted -> {
                    //permission granted
                    permissionChanged(currentIndex, permissionState[currentIndex].isGranted)
                    rationalChanged(currentIndex, false)
                    currentIndex++
                }

                ActivityCompat.shouldShowRequestPermissionRationale(
                    context as Activity,
                    permissionState[currentIndex].permissionName
                ) -> {
                    //show rationale dialog
                    rationalChanged(currentIndex, true)
                }

                else -> {
                    //no permission, request it
                    rationalChanged(currentIndex, false)
                    requestPermissions.launch(arrayOf(permissionState[currentIndex].permissionName))
                }
            }
        }
    }

    if (onResumeIndex in 0 .. permissionState.size-1 && permissionState[onResumeIndex].showRational) {
        AlertDialog(
            onDismissRequest = {
                val isGranted =
                    context.checkSelfPermission(permissionState[onResumeIndex].permissionName) == PackageManager.PERMISSION_GRANTED
                permissionChanged(onResumeIndex, isGranted)
                rationalChanged(onResumeIndex, false)
                onResumeIndex++
            },
            text = {
                Text(text = permissionState[onResumeIndex].message)
            },
            confirmButton = {
                TextButton(onClick = {
                    goToSettingsRequest.launch(
                        Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
                            Uri.fromParts("package", context.packageName, null)
                        ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    )

                }) {
                    Text(text = "Open App Settings")
                }
            }
        )
    }
}

This Post Has One Comment

Leave a Reply