Comprehensive Guide to Android Notifications Implementation

Notifications serve as a primary mechanism for Android applications to display messages outside the standard user interface. They typically appear in the status bar at the top of the screen, providing users with reminders, communication from others, or timely information from the app. Users can interact with notifications by tapping them to open the app or performing actions directly within the notification interface.

When a notification is issued, it appears as an icon in the status bar. Users can swipe down to access the notification drawer, revealing detailed information. The system handles the visual representation, determining how notifications appear in different contexts, such as lock screens or wearable devices.

Platform Version Adaptatiosn

Android has evolved significantly regarding notification handling. Developers must account for these changes to ensure compatibility.

  • Android 5.0 (API 21): Introduced lock screen notifications and "Heads-up" notifications (floating banners). Added setVisibility() to control lock screen visibility and setPriority() to influence interruption level.
  • Android 7.0 (API 24): Enhanced notification templates and introduced Notification Groups (bundling). It also enabled direct reply functionality within notifications.
  • Android 8.0 (API 26): Introduced Notification Channels. Developers must now categorize notifications into channels, allowing users to manage preferences per channel rather than per app. It also introduced notification badges on app icons.
  • Android 12 (API 31): Enforced strict requirements on PendingIntent mutability. Developers must explicitly specify FLAG_IMMUTABLE or FLAG_MUTABLE.
  • Android 13 (API 33): Introduced the POST_NOTIFICATIONS runtime permission. Apps must request user permission to display non-foreground service notifications.
  • Android 14 (API 34): Restricted full-screen intent notifications to specific use cases like alarms and calls.

Anatomy of a Notification

The visual layout is determined by system templates. A standard notification consists of the following key elements:

  • Small Icon (Required): Set via setSmallIcon(). Represents the app in the status bar.
  • App Name: Provided by the system.
  • Timestamp: Provided by the system, customizable via setWhen().
  • Large Icon: Optional, often used for contact photos. Set via setLargeIcon().
  • Title: Set via setContentTitle().
  • Text: Set via setContentText().

Importance and Priority

Starting from Android 8.0 (API 26), the "importance" of a notification is determined by the Notification Channel it belongs to. For older versions, priority is set on the builder itself. Importance levels dictate how intrusive the notification is:

Level Description
IMPORTANCE_HIGH Makes a sound and appears as a heads-up notification. Use for urgent real-time communication.
IMPORTANCE_DEFAULT Makes a sound but does not visually intrude on the screen.
IMPORTANCE_LOW No sound. Shows in the shade but not on the status bar (though the icon appears).
IMPORTANCE_MIN No sound and no status bar icon. Only visible in the full notification shade.

Implementation Steps

The core classes involved are NotificationManager, NotificationChannel (API 26+), and NotificationCompat.Builder.

  1. Create Channel: For Android 8.0 and above, create a channel to group notifications.
  2. Build Notification: Use NotificationCompat.Builder for backward compatibility.
  3. Configure Content: Set icons, title, text, and tap intent.
  4. Notify: Use NotificationManager.notify() to post the notification.

Basic Notification Example

val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = "primary_channel_id"

// 1. Create Channel (Required for API 26+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val channel = NotificationChannel(
        channelId,
        "General Updates",
        NotificationManager.IMPORTANCE_DEFAULT
    ).apply {
        description = "Channel for general app notifications"
    }
    manager.createNotificationChannel(channel)
}

// 2. Define Tap Action (PendingIntent)
val intent = Intent(this, DetailActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
    this, 
    0, 
    intent, 
    PendingIntent.FLAG_IMMUTABLE
)

// 3. Build Notification
val builder = NotificationCompat.Builder(this, channelId)
    .setSmallIcon(R.drawable.ic_notification)
    .setContentTitle("New Alert")
    .setContentText("This is a standard notification message.")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT) // Legacy support
    .setContentIntent(pendingIntent)
    .setAutoCancel(true) // Dismiss on tap

// 4. Issue Notification
manager.notify(101, builder.build())

PendingIntent Mutability

Starting from Android 12 (API 31), creating a PendingIntent requires specifying mutability flags. If omitted, the system throws an exception.

  • FLAG_IMMUTABLE: Recommended for most cases. The wrapped Intent cannot be modified by the receiver.
  • FLAG_MUTABLE: Required if the Intent contents need to be updated (e.g., adding extras in a notification reply action).

Advanced Styles

The setStyle() method alows the use of rich notification templates.

Progress Bar Notification

Useful for ongoing operations like file downloads. The system handles the visual update; you simply update the same notification ID with new progress values.

val builder = NotificationCompat.Builder(this, channelId)
    .setSmallIcon(R.drawable.ic_download)
    .setContentTitle("Downloading File")
    .setContentText("Download in progress")
    .setOngoing(true) // Prevents swiping away
    .setProgress(100, 0, false)

manager.notify(notificationId, builder.build())

// Inside your download loop
Thread {
    for (progress in 0..100) {
        Thread.sleep(100) // Simulate work
        builder.setProgress(100, progress, false)
        manager.notify(notificationId, builder.build())
    }
    // Completion
    builder.setContentText("Download complete")
           .setProgress(0, 0, false)
           .setOngoing(false)
    manager.notify(notificationId, builder.build())
}.start()

Big Picture Style

Displays a large image when the notification is expanded.

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image)
val style = NotificationCompat.BigPictureStyle()
    .bigPicture(bitmap)
    .bigLargeIcon(null) // Optional: hide large icon when expanded

val builder = NotificationCompat.Builder(this, channelId)
    .setSmallIcon(R.drawable.ic_notification)
    .setContentTitle("Image Received")
    .setContentText("Expand to view image")
    .setStyle(style)
    .setLargeIcon(bitmap)

Custom Layouts

For unique UI requirements, you can define a custom layout using RemoteViews. Unlike standard layouts, RemoteViews describe a view hierarchy that runs in a remote process (the system UI). Consequently, event handling relies heavily on PendingIntent.

// Define RemoteViews
val remoteViews = RemoteViews(packageName, R.layout.layout_custom_notification)

// Set text and images
remoteViews.setTextViewText(R.id.txt_title, "Custom Title")
remoteViews.setImageViewResource(R.id.img_icon, R.drawable.ic_music)

// Handle Button Clicks via BroadcastReceiver
val playIntent = Intent("ACTION_PLAY")
val playPendingIntent = PendingIntent.getBroadcast(
    this, 0, playIntent, PendingIntent.FLAG_IMMUTABLE
)
remoteViews.setOnClickPendingIntent(R.id.btn_play, playPendingIntent)

// Apply to Notification
val customNotification = NotificationCompat.Builder(this, channelId)
    .setSmallIcon(R.drawable.ic_notification)
    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
    .setCustomContentView(remoteViews)
    .build()

manager.notify(200, customNotification)

When using custom layouts, ensure they adapt to different screen sizes and orientations. The height constraint are strict: collapsed views are limited to approximately 48dp, while expanded views can reach roughly 256dp.

Tags: Android notification kotlin NotificationChannel PendingIntent

Posted on Thu, 27 Aug 2026 16:47:08 +0000 by marijn