Challenge 3: Graceful Denial Handling — Solution @Composable fun TaskScreenWithNotifications() { val context = LocalContext.current val activity = context as Activity var isGranted by remember { mutableStateOf( ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED ) } var wasDenied by remember { mutableStateOf(false) } val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission() ) { granted -> isGranted = granted if (!granted) wasDenied = true } Column(modifier = Modifier.padding(16.dp)) { // The core screen content is ALWAYS shown, regardless of permission state Text(text = "Tasks: 3") Spacer(modifier = Modifier.height(12.dp)) when { isGranted -> { Text("Notifications are enabled") } wasDenied && ActivityCompat.shouldShowRequestPermissionRationale( activity, Manifest.permission.POST_NOTIFICATIONS ) -> { Text("Notifications help you stay on top of tasks") Button(onClick = { launcher.launch(Manifest.permission.POST_NOTIFICATIONS) }) { Text("Try Again") } } wasDenied -> { Text("Enable notifications in Settings") } else -> { Button(onClick = { launcher.launch(Manifest.permission.POST_NOTIFICATIONS) }) { Text("Enable Notifications") } } } } } Notes: - Text(text = "Tasks: 3") is placed OUTSIDE the permission-related when block entirely — it's part of the screen's core content and renders unconditionally, confirming the app remains fully usable regardless of whether notifications are granted, denied, or never asked about. - shouldShowRequestPermissionRationale returning true means the user denied once but the system will still show the request dialog again — that's exactly when an explanatory message before re-asking is most useful, since a bare re-prompt with no context tends to just get denied again. - Once shouldShowRequestPermissionRationale returns false AFTER a denial (as opposed to before ever asking, which also returns false), the system has permanently blocked the dialog — the only remaining option is directing the user to the app's Settings screen manually, which is why that branch shows different text with no retry button at all. - context as Activity is needed because ActivityCompat.shouldShowRequestPermissionRationale requires an Activity specifically, not just any Context — LocalContext.current in a normal Activity-hosted composable is safely castable this way.