Permission requests showing up out of order


I need to request POST_NOTIFICATIONS, and READ_PHONE_STATE

This:

@Composable
fun canPushNotifications () {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        val launcher = rememberLauncherForActivityResult(
            contract = ActivityResultContracts.RequestPermission()
        ) {"""on permission granted"""}
        val lifecycleOwner = LocalLifecycleOwner.current
        LaunchedEffect(lifecycleOwner) {
            lifecycleOwner.lifecycle.withStarted {
                launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
            }
        }
    }
}
@Composable
fun canReadPhoneState (): Boolean {
    val context = LocalContext.current
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.READ_PHONE_STATE), 1)
    }
    return ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.READ_PHONE_STATE
    ) == PackageManager.PERMISSION_GRANTED
}

and this:

@Composable
fun canPushNotifications(): Boolean {
    val context = LocalContext.current
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0)
        }
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) == PackageManager.PERMISSION_GRANTED
    } else {
        return true
    }
}

@Composable
fun canReadPhoneState (): Boolean {
    val context = LocalContext.current
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.READ_PHONE_STATE), 1)
    }
    return ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.READ_PHONE_STATE
    ) == PackageManager.PERMISSION_GRANTED
}

In both these sets of functions, permission 2 shows up only when a: permission 1 has been granted and b: the app has been restarted. a always happens first and b, last.

Version 1: the two permission request functions are structurally different. canPushNotifications has a callback, and a "Wait for lifecycle"

Function call order: POST_NOTIFICATIONS, then READ_PHONE_STATE

behavior: READ_PHONE_STATE first, app restart, then POST_NOTIFICATIONS Them being reversed makes sense, because of the LifeCycleOwner part. what doesn't make sense is the part where I have to re-launch the app to see the second permission dialogue

Version 2: both permission request functions are structurally identical.

Function call order: same

behavior: POST_NOTIFICATIONS first, app restart, then READ_PHONE_STATE. I still have to restart the app to see the second permission request dialogue, but at least they're in order.

In both versions of MainActivity.kt (the file this code is from), the order in which these two functions are called does not change.

canPushNotification()
canReadPhoneState()

why do I need to restart the app to view the second permission request?
How do I fix this?

I am available to respond 7 days a week

0
Jul 9 at 5:08 AM
User AvatarProject Awesome
#android

Accepted Answer

You can try like the following options

Option 1: Request Multiple Permissions at Once (Recommended)

@Composable
fun RequestAllPermissions(onComplete: (Map<String, Boolean>) -> Unit) {
    val requiredPermissions = buildList {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            add(Manifest.permission.POST_NOTIFICATIONS)
        }
        add(Manifest.permission.READ_PHONE_STATE)
    }
    
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        // This callback fires once with results for ALL permissions
        // permissions is a Map<String, Boolean> like:
        // {"POST_NOTIFICATIONS" -> true, "READ_PHONE_STATE" -> false}
        onComplete(permissions)
    }
    
    LaunchedEffect(Unit) {
        launcher.launch(requiredPermissions.toTypedArray())
    }
}

// Usage in your MainActivity Composable:
@Composable
fun MainActivityContent() {
    var permissionsChecked by remember { mutableStateOf(false) }
    var notificationsGranted by remember { mutableStateOf(false) }
    var phoneStateGranted by remember { mutableStateOf(false) }
    
    if (!permissionsChecked) {
        RequestAllPermissions { permissionResults ->
            notificationsGranted = permissionResults[Manifest.permission.POST_NOTIFICATIONS] ?: true
            phoneStateGranted = permissionResults[Manifest.permission.READ_PHONE_STATE] ?: true
            permissionsChecked = true
        }
    } else {
        // All permissions processed, show your app content
        AppContent(
            notificationsEnabled = notificationsGranted,
            phoneStateEnabled = phoneStateGranted
        )
    }
}

Option 2: Chain Permissions Sequentially (One After Another)

@Composable
fun SequentialPermissionRequester(
    onAllPermissionsComplete: (Boolean) -> Unit
) {
    val context = LocalContext.current as Activity
    var currentStep by remember { mutableStateOf(0) } // 0 = notifications, 1 = phone, 2 = done
    
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        when (currentStep) {
            0 -> {
                // POST_NOTIFICATIONS result received
                currentStep = 1
                // Request next permission
                launcher.launch(Manifest.permission.READ_PHONE_STATE)
            }
            1 -> {
                // READ_PHONE_STATE result received
                currentStep = 2
                onAllPermissionsComplete(true)
            }
        }
    }
    
    LaunchedEffect(Unit) {
        // Start with first permission
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
        } else {
            // Skip to phone state on older versions
            currentStep = 1
            launcher.launch(Manifest.permission.READ_PHONE_STATE)
        }
    }
}

// Usage:
@Composable
fun MainActivityContent() {
    var permissionsComplete by remember { mutableStateOf(false) }
    
    if (!permissionsComplete) {
        SequentialPermissionRequester { allGranted ->
            permissionsComplete = true
            // Handle result (allGranted tells you if the flow completed)
        }
    } else {
        AppContent()
    }
}

### Option 3: State-Based Approach with ViewModel (Best for Complex Apps)
class PermissionViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    data class PermissionState(
        val notificationsGranted: Boolean = false,
        val phoneStateGranted: Boolean = false,
        val allChecked: Boolean = false
    )
    
    private val _state = MutableStateFlow(PermissionState())
    val state: StateFlow<PermissionState> = _state.asStateFlow()
    
    fun setNotificationsGranted(granted: Boolean) {
        _state.update { it.copy(notificationsGranted = granted) }
    }
    
    fun setPhoneStateGranted(granted: Boolean) {
        _state.update { it.copy(phoneStateGranted = granted, allChecked = true) }
    }
}

@Composable
fun PermissionHandler(viewModel: PermissionViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsState()
    val context = LocalContext.current as Activity
    
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (!state.notificationsGranted) {
            // First permission (POST_NOTIFICATIONS) just completed
            viewModel.setNotificationsGranted(isGranted)
            // Now request second permission
            launcher.launch(Manifest.permission.READ_PHONE_STATE)
        } else {
            // Second permission (READ_PHONE_STATE) just completed
            viewModel.setPhoneStateGranted(isGranted)
        }
    }
    
    LaunchedEffect(state.allChecked) {
        if (!state.allChecked) {
            // Start with first permission
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
            } else {
                viewModel.setNotificationsGranted(true)
                launcher.launch(Manifest.permission.READ_PHONE_STATE)
            }
        }
    }
    
    if (state.allChecked) {
        // All permissions processed
        AppContent(
            notificationsEnabled = state.notificationsGranted,
            phoneStateEnabled = state.phoneStateGranted
        )
    }
    // Otherwise show loading or nothing
}
User AvatarMohimenol Islam Fahim
Jul 9 at 7:15 AM
1