Notifications keep users informed outside your app. Permissions gate access to sensitive device features like camera, location, and contacts. Modern Android requires explicit user consent for both.
Local notification → Your app builds and shows via NotificationManager
Push notification → FCM server sends → FirebaseMessagingService → show
Runtime permission → User grants at runtime for "dangerous" permissions
Topic
Android Version
Key Requirement
Notification Channels
8.0+
Every notification must belong to a channel
POST_NOTIFICATIONS
13+
Runtime permission to show notifications
Runtime Permissions
6.0+
Request dangerous permissions at runtime
FCM Push
All
Firebase project + google-services.json
1Notification Channels
Since Android 8.0, every notification must be assigned to a NotificationChannel. Users can control sound, vibration, and visibility per channel in system settings.
Manifest permission (Android 13+)
XML — AndroidManifest.xml<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Create notification channels
Kotlin — NotificationHelper.ktobject NotificationHelper {
const val CHANNEL_MESSAGES = "messages"
const val CHANNEL_UPDATES = "updates"
const val CHANNEL_ALERTS = "alerts"
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = context.getSystemService(NotificationManager::class.java)
val messagesChannel = NotificationChannel(
CHANNEL_MESSAGES,
"Messages",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Direct messages and chat notifications"
enableVibration(true)
vibrationPattern = longArrayOf(0, 250, 100, 250)
}
val updatesChannel = NotificationChannel(
CHANNEL_UPDATES,
"App Updates",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "New features and content updates"
}
val alertsChannel = NotificationChannel(
CHANNEL_ALERTS,
"Alerts",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Non-urgent reminders"
setShowBadge(false)
}
manager.createNotificationChannels(
listOf(messagesChannel, updatesChannel, alertsChannel)
)
}
}
}
Kotlin — MyFirebaseMessagingService.ktclass MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val title = message.notification?.title ?: message.data["title"] ?: "New Message"
val body = message.notification?.body ?: message.data["body"] ?: ""
showMessageNotification(applicationContext, title, body)
}
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d("FCM", "New token: $token")
// Send token to your backend server
sendTokenToServer(token)
}
}
Android classifies permissions as normal (granted at install) or dangerous (require runtime user approval). Always check before accessing protected features.
Request permissions at the moment they're needed — not at app launch. Use ActivityResultContracts.RequestPermission and explain why before asking if the user previously denied.
Single permission request
Kotlin — Request camera permissionclass MainActivity : AppCompatActivity() {
private val requestCameraPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCamera()
} else {
Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show()
}
}
fun onTakePhotoClick() {
when {
hasCameraPermission(this) -> openCamera()
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
AlertDialog.Builder(this)
.setTitle("Camera Permission")
.setMessage("We need camera access to take profile photos.")
.setPositiveButton("Grant") { _, _ ->
requestCameraPermission.launch(Manifest.permission.CAMERA)
}
.setNegativeButton("Cancel", null)
.show()
}
else -> requestCameraPermission.launch(Manifest.permission.CAMERA)
}
}
}
Multiple permissions at once
Kotlin — Request multiple permissionsprivate val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] == true
val locationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true
if (cameraGranted && locationGranted) {
startPhotoWithLocation()
} else {
Toast.makeText(this, "Some permissions were denied", Toast.LENGTH_SHORT).show()
}
}
fun requestCameraAndLocation() {
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
}
Request POST_NOTIFICATIONS (Android 13+)
Kotlin — Notification permissionprivate val requestNotificationPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
showWelcomeNotification()
}
}
fun checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
Permission request flow
User taps feature needing permission
↓
Already granted? → Use feature
↓
shouldShowRequestPermissionRationale? → Show explanation dialog
↓
Launch permission request
↓
Granted → Use feature | Denied → Show fallback / settings link
5Notification Actions
Notification actions add interactive buttons to notifications — Reply, Mark Read, Snooze, or Open — without launching the full app.
Notification with action buttons
Kotlin — Reply and dismiss actionsfun showMessageWithActions(context: Context, messageId: Int, sender: String, text: String) {
val openIntent = Intent(context, ChatActivity::class.java).apply {
putExtra("message_id", messageId)
}
val openPending = PendingIntent.getActivity(
context, messageId, openIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val replyIntent = Intent(context, ReplyReceiver::class.java).apply {
action = "ACTION_REPLY"
putExtra("message_id", messageId)
}
val replyPending = PendingIntent.getBroadcast(
context, messageId, replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val dismissIntent = Intent(context, DismissReceiver::class.java).apply {
action = "ACTION_DISMISS"
putExtra("message_id", messageId)
}
val dismissPending = PendingIntent.getBroadcast(
context, messageId + 1000, dismissIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MESSAGES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(sender)
.setContentText(text)
.setContentIntent(openPending)
.addAction(R.drawable.ic_reply, "Reply", replyPending)
.addAction(R.drawable.ic_dismiss, "Dismiss", dismissPending)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context).notify(messageId, notification)
}
Kotlin — Reply directly from notificationval remoteInput = RemoteInput.Builder("key_reply")
.setLabel("Type a reply...")
.build()
val replyIntent = Intent(context, ReplyReceiver::class.java)
val replyPending = PendingIntent.getBroadcast(
context, 0, replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val replyAction = NotificationCompat.Action.Builder(
R.drawable.ic_reply, "Reply", replyPending
)
.addRemoteInput(remoteInput)
.build()
val notification = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MESSAGES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Nikhil")
.setContentText("Hey, are you free?")
.addAction(replyAction)
.build()
Handle reply in Receiver
Kotlin — ReplyReceiver.ktclass ReplyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val remoteInput = RemoteInput.getResultsFromIntent(intent)
val replyText = remoteInput?.getCharSequence("key_reply")?.toString()
if (!replyText.isNullOrBlank()) {
Log.d("Reply", "User replied: $replyText")
// Send reply via API or local database
}
}
}
6Hands-On Exercises
1
Create three notification channels (Messages, Updates, Alerts) with different importance levels. Show a test notification on each.
2
Request POST_NOTIFICATIONS permission on Android 13+ before showing any notification.
3
Set up Firebase Cloud Messaging. Log the FCM token and display a notification when a test push arrives.
4
Implement camera permission request with rationale dialog when the user taps "Take Photo".
5
Request multiple permissions (camera + location) together and handle partial grants gracefully.
6
Add notification actions: "Open" launches the app, "Dismiss" cancels the notification via BroadcastReceiver.
7
Bonus: Implement an inline Reply action with RemoteInput so users can respond from the notification shade.
Notifications & Permissions MCQ Practice
10 Basic MCQs10 Advanced MCQs
10 Basic Notifications & Permissions MCQs
1
Notification channels required since?
AAndroid 4
BAndroid 8 (Oreo)
CAndroid 14 only
DNever required
Explanation: Users control channel settings per category.
2
POST_NOTIFICATIONS permission needed on?
ANever
BAll versions always
CAndroid 13+ at runtime
DAndroid 5 only
Explanation: Request runtime permission to show notifications on API 33+.
3
NotificationCompat.Builder requires?
AGradle sync
BSQLite database
CCamera
DContext and channel ID
Explanation: Use NotificationCompat for backward compatibility.
4
Dangerous permissions require?
ARuntime request from user
BManifest only always
CPlay Console approval
DRoot access
Explanation: Camera, location, storage etc. need runtime grant.
5
shouldShowRequestPermissionRationale?
AAlways false
BTrue when show explanation before re-request
CGrants permission
DRevokes permission
Explanation: Display rationale UI when returns true.
6
NotificationManager notifies with?
AsendBroadcast only
BstartActivity only
Cnotify(id, notification)
DRoom insert
Explanation: Unique ID per notification; update with same ID.
7
FCM delivers?
ADex optimization
BLocal SQL only
CLayout inflation
DPush messages from server to device
Explanation: Firebase Cloud Messaging for remote push.
8
Permission declared in?
AAndroidManifest.xml
Blayout XML only
CGradle wrapper
Dstrings.xml only
Explanation: Must declare before requesting at runtime.
9
Notification action uses?
ADirect Activity start only
BPendingIntent on action button
CSQLite trigger
DSensor event
Explanation: PendingIntent wraps Intent for button taps from notification.