Notifications & Permissions

Notify users effectively and request permissions the right way

Channels Push (FCM) Runtime Permissions Actions

Table of Contents

Notifications & Permissions Overview

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
TopicAndroid VersionKey Requirement
Notification Channels8.0+Every notification must belong to a channel
POST_NOTIFICATIONS13+Runtime permission to show notifications
Runtime Permissions6.0+Request dangerous permissions at runtime
FCM PushAllFirebase 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) ) } } }

Show a local notification

Kotlin — Display notificationfun showMessageNotification(context: Context, title: String, body: String) { NotificationHelper.createChannels(context) val intent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val pendingIntent = PendingIntent.getActivity( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MESSAGES) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(title) .setContentText(body) .setStyle(NotificationCompat.BigTextStyle().bigText(body)) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true) .build() NotificationManagerCompat.from(context).notify(1001, notification) }

Importance levels

ImportanceBehavior
IMPORTANCE_HIGHHeads-up popup, sound, status bar
IMPORTANCE_DEFAULTSound, status bar, no heads-up
IMPORTANCE_LOWStatus bar only, no sound
IMPORTANCE_MINCollapsed in shade, no badge

2Push Notifications

Push notifications are sent from a server to devices via Firebase Cloud Messaging (FCM). They arrive even when the app is not running.

Setup steps

  1. Create a Firebase project at console.firebase.google.com
  2. Add Android app with your package name
  3. Download google-services.json into app/
  4. Add Firebase Messaging dependency

Gradle setup

Kotlin — build.gradle.kts// Project-level plugins { id("com.google.gms.google-services") version "4.4.1" apply false } // App-level plugins { id("com.google.gms.google-services") } dependencies { implementation(platform("com.google.firebase:firebase-bom:32.7.2")) implementation("com.google.firebase:firebase-messaging-ktx") }

FirebaseMessagingService

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) } }

Manifest service declaration

XML — AndroidManifest.xml<service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>

Get FCM token in Activity

Kotlin — Retrieve device tokenFirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (task.isSuccessful) { val token = task.result Log.d("FCM", "Device token: $token") } }

3Runtime Permissions

Android classifies permissions as normal (granted at install) or dangerous (require runtime user approval). Always check before accessing protected features.

Permission groups (dangerous)

PermissionGroupUse Case
CAMERACameraTake photos, scan QR
ACCESS_FINE_LOCATIONLocationGPS, maps, nearby
READ_CONTACTSContactsImport contacts
RECORD_AUDIOMicrophoneVoice notes, calls
READ_MEDIA_IMAGESStorage (13+)Pick photos from gallery
POST_NOTIFICATIONSNotifications (13+)Show notifications

Declare in manifest

XML — AndroidManifest.xml<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

Check permission before use

Kotlin — Check if grantedfun hasCameraPermission(context: Context): Boolean { return ContextCompat.checkSelfPermission( context, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED } fun hasLocationPermission(context: Context): Boolean { return ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED }

4Permission Requests

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) }

BroadcastReceiver for action

Kotlin — DismissReceiver.ktclass DismissReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val messageId = intent.getIntExtra("message_id", -1) if (messageId != -1) { NotificationManagerCompat.from(context).cancel(messageId) Log.d("Notification", "Dismissed message $messageId") } } }

Inline reply action (RemoteInput)

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 MCQs 10 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.
10

ActivityResultContracts.RequestPermission?

ANotification channel
BDeprecated API
CModern permission request launcher
DFCM token
Explanation: registerForActivityResult replaces onRequestPermissionsResult.

10 Advanced Notifications & Permissions MCQs

11

PendingIntent mutability flags?

AAlways mutable
BFLAG_IMMUTABLE required on Android 12+
CNot needed
DDeprecated entirely
Explanation: Use FLAG_IMMUTABLE unless explicit mutable needed.
12

Notification channel importance LOW?

ABypass DND always
BHeads-up always
CNo sound/peek; minimal interruption
DSilent banned
Explanation: IMPORTANCE_HIGH for heads-up; DEFAULT for sound.
13

Exact alarm permission?

ANever needed
BAutomatic
CSame as POST_NOTIFICATIONS
DSCHEDULE_EXACT_ALARM for precise alarms Android 12+
Explanation: Exact alarms restricted for battery — use inexact when possible.
14

Grouped notifications?

AsetGroup + summary notification
BImpossible
COne channel only
DRequires root
Explanation: Bundle related notifications with group key.
15

Foreground service notification channel?

ASame as marketing
BSeparate channel recommended for ongoing tasks
COptional illegal
DBanned
Explanation: User can distinguish ongoing vs alerts.
16

Permission permanently denied?

ACrash app
BRequest again in loop
CUser checked don't ask again — guide to app settings
DAuto grant
Explanation: Open Settings via Intent ACTION_APPLICATION_DETAILS_SETTINGS.
17

Notification trampolines restricted?

AOnly local
BAllowed always
COnly FCM
DCannot start Activity from notification trampolines on 12+
Explanation: Start Service/Broadcast instead or use full-screen intent carefully.
18

Multiple permissions request?

ARequestMultiplePermissions contract
BOne at a time only always
CManifest merge
DImpossible
Explanation: ActivityResultContracts.RequestMultiplePermissions.
19

BigTextStyle notification?

AVideo only
BExpandable long text style
CSQL dump
DGPS map
Explanation: NotificationCompat.BigTextStyle().bigText(longMessage).
20

FCM data vs notification payload?

AData only on iOS
BSame always
CData handled in app code; notification auto-shown when app background
DNeither works
Explanation: Data messages give full control; notification payload system displays when background.
Click an option to select, then check answers.

Notifications & Permissions Interview Q&A

15 topic-focused questions for interviews and revision.

1Create notification channel.easy
Answer: NotificationChannel(id, name, importance); notificationManager.createNotificationChannel(channel)
2Request POST_NOTIFICATIONS on Android 13.easy
Answer: registerForActivityResult(RequestPermission()) { granted -> }; launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
3Show basic notification.easy
Answer: Build NotificationCompat.Builder(ctx, CHANNEL_ID).setContentTitle().setSmallIcon().build(); nm.notify(id, notif)
4Dangerous vs normal permissions.easy
Answer: Normal granted at install; dangerous require runtime user approval.
5Handle permission denied.medium
Answer: Disable feature, show explanation, offer link to settings if permanently denied.
6Notification with action button.medium
Answer: addAction(icon, "Reply", replyPendingIntent) on builder.
7FCM setup overview.medium
Answer: Add Firebase, google-services.json, FCM dependency, FirebaseMessagingService, request token, send from server.
8Why channels matter.easy
Answer: Users mute categories; proper channels improve UX and Play policy compliance.
9PendingIntent request codes.medium
Answer: Unique requestCode per PendingIntent variant to avoid intent collision.
10Full-screen intent use.hard
Answer: High-priority incoming call/alarm — requires USE_FULL_SCREEN_INTENT permission on 14+.
11Test notifications on emulator.easy
Answer: Create channel, grant POST_NOTIFICATIONS, trigger from debug button or FCM test message.
12Permission rationale UI.medium
Answer: If shouldShowRequestPermissionRationale, show dialog explaining why before re-request.
13Cancel notification.easy
Answer: NotificationManager.cancel(id) or cancelAll().
14Background location permission.hard
Answer: Separate BACKGROUND_LOCATION after foreground location granted — strict Play policy.
15Notification best practices.medium
Answer: Timely, relevant, actionable; respect DND; don't spam; use appropriate importance.