Designing Notification Systems

📖 Concept

Notification systems on Android are complex due to channels, priority levels, permissions (Android 13+), and battery restrictions. A well-designed notification system drives engagement without annoying users.

Android notification architecture:

Server → FCM → Device → App Process → NotificationManager → System UI
                                    → WorkManager (data sync)

Notification channels (Android 8.0+):

  • Each app must create notification channels for different types
  • Users can individually control each channel's importance, sound, vibration
  • Cannot modify channel importance after creation (must delete and recreate)

FCM message types:

  1. Data messages: Handled by your app's FirebaseMessagingService. App controls notification display. Works in foreground and background.
  2. Notification messages: Displayed automatically by the system when app is in background. Handled by your app only when in foreground.
  3. Data + Notification: Combined. System shows notification in background; your app handles in foreground.

Best practice: Always use data-only messages — they give you full control over notification display and allow you to sync data before showing the notification.

Notification permission (Android 13+): Must request POST_NOTIFICATIONS runtime permission. Without it, notifications are silently suppressed.

💻 Code Example

codeTap to expand ⛶
1// Complete notification system implementation
2
3// 1. Create notification channels at app startup
4class NotificationChannelManager @Inject constructor(
5 @ApplicationContext private val context: Context
6) {
7 fun createChannels() {
8 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
9
10 val channels = listOf(
11 NotificationChannel(
12 "messages", "Messages",
13 NotificationManager.IMPORTANCE_HIGH
14 ).apply { description = "New message notifications" },
15
16 NotificationChannel(
17 "updates", "App Updates",
18 NotificationManager.IMPORTANCE_DEFAULT
19 ).apply { description = "Feature updates and news" },
20
21 NotificationChannel(
22 "sync", "Background Sync",
23 NotificationManager.IMPORTANCE_LOW
24 ).apply {
25 description = "Data synchronization"
26 setShowBadge(false)
27 }
28 )
29
30 val manager = context.getSystemService(NotificationManager::class.java)
31 manager.createNotificationChannels(channels)
32 }
33}
34
35// 2. FCM Service — handle data messages
36class MyFirebaseService : FirebaseMessagingService() {
37 override fun onMessageReceived(message: RemoteMessage) {
38 val data = message.data
39 when (data["type"]) {
40 "new_message" -> handleNewMessage(data)
41 "sync_trigger" -> scheduleSyncWork()
42 "silent_update" -> handleSilentUpdate(data)
43 }
44 }
45
46 private fun handleNewMessage(data: Map<String, String>) {
47 val notification = NotificationCompat.Builder(this, "messages")
48 .setSmallIcon(R.drawable.ic_message)
49 .setContentTitle(data["sender_name"])
50 .setContentText(data["preview"])
51 .setAutoCancel(true)
52 .setContentIntent(createPendingIntent(data["conversation_id"]))
53 .setStyle(NotificationCompat.MessagingStyle(selfPerson)
54 .addMessage(data["preview"], System.currentTimeMillis(), senderPerson))
55 .addAction(R.drawable.ic_reply, "Reply", createReplyPendingIntent())
56 .build()
57
58 NotificationManagerCompat.from(this).notify(
59 data["conversation_id"].hashCode(),
60 notification
61 )
62 }
63
64 override fun onNewToken(token: String) {
65 // Send new token to your server
66 CoroutineScope(Dispatchers.IO).launch {
67 api.updateFcmToken(token)
68 }
69 }
70}
71
72// 3. Request notification permission (Android 13+)
73@Composable
74fun NotificationPermissionRequest() {
75 val permissionState = rememberPermissionState(
76 Manifest.permission.POST_NOTIFICATIONS
77 )
78
79 LaunchedEffect(Unit) {
80 if (!permissionState.status.isGranted) {
81 permissionState.launchPermissionRequest()
82 }
83 }
84}

🏋️ Practice Exercise

Practice:

  1. Implement notification channels for 3 different notification types
  2. Handle FCM data messages and display rich notifications with actions
  3. Implement notification grouping for multiple messages from the same sender
  4. Set up FCM token refresh and server registration
  5. Handle the Android 13 POST_NOTIFICATIONS permission gracefully

⚠️ Common Mistakes

  • Using notification messages instead of data messages — you lose control over notification content and behavior in background

  • Not creating notification channels on Android 8.0+ — notifications silently fail

  • Sending FCM token only once — tokens can refresh, always handle onNewToken

  • Not requesting POST_NOTIFICATIONS permission on Android 13+ — notifications are suppressed

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Designing Notification Systems. Login to unlock this feature.