Notifications in Jetpack Compose

Notifications in Jetpack Compose

Mastering Notifications in Android Using Jetpack Compose

By CodingBihar.com

In the ever-evolving world of Android development, Jetpack Compose has emerged as the modern toolkit for building native UI. But when it comes to system-level features like notifications, many developers still rely on XML-based approaches. So, the question is: Can we manage notifications effectively using Jetpack Compose? The answer is yes — and in this article, we’ll explore exactly how!

Whether you’re building a chat app, a music player, or a reminder tool, notifications play a crucial role in keeping users engaged. Let’s dive deep into how to implement and manage notifications the Compose way.

What Are Android Notifications?

Android notifications are messages shown to users outside your app's UI. They appear in the notification drawer, lock screen, or even as heads-up alerts.

You can use them to:

  • Alert users about new messages or events
  • Show progress (like downloads or uploads)
  • Allow quick actions (like reply, mark as read, play/pause music)

Jetpack Compose and Notifications: Understanding the Basics

Jetpack Compose itself doesn’t provide direct APIs for notifications because they are part of the Android system (not UI). So, we still use the Notification APIs from the Android SDK, but we integrate them seamlessly within Compose-based apps.

Let’s look at a basic example step-by-step.

Step-by-Step: Creating a Simple Notification

1. Add Required Permissions (Optional)

No special permissions are needed for basic notifications, but for Android 13+ (API 33+), you must request POST_NOTIFICATIONS permission at runtime.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

2. Request Notification Permission (Android 13+)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    ActivityCompat.requestPermissions(
        context as Activity,
        arrayOf(Manifest.permission.POST_NOTIFICATIONS),
        1
    )
}

3. Create a Notification Channel (For Android 8.0+)

fun createNotificationChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = "General"
        val descriptionText = "General Notifications"
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel("channel_id", name, importance).apply {
            description = descriptionText
        }
        val notificationManager: NotificationManager =
            context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

Call this function once in MainActivity.

4. Build and Show the Notification

fun showNotification(context: Context, title: String, message: String) {
    val builder = NotificationCompat.Builder(context, "channel_id")
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(message)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)

    with(NotificationManagerCompat.from(context)) {
        notify(1001, builder.build())
    }
}

5. Trigger the Notification from Jetpack Compose UI

@Composable
fun NotificationScreen() {
    val context = LocalContext.current

    LaunchedEffect(Unit) {
        createNotificationChannel(context)
    }

    Button(onClick = {
        showNotification(context, "Hello from CodingBihar", "This is your first Jetpack Compose Notification!")
    }) {
        Text("Show Notification")
    }
}
That’s it! You’ve now built a working notification system in your Jetpack Compose app.

Full Coode

Manifest Notification Permission:

Loading...

MainActivity

package com.codingbihar.composenotificationdemo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.codingbihar.composenotificationdemo.ui.theme.ComposeNotificationDemoTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ComposeNotificationDemoTheme {

MessageDemo()
}
}
}
}

Notification Screen

package com.codingbihar.composenotificationdemo

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.Build
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat

@Composable
fun MessageDemo() {

val context = LocalContext.current
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted ->
val msg = if (isGranted) "Notification permission granted" else "Notification permission denied"
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
)

// Ask permission & create notification channel
LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
showNotification(context)
}
Column (Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Button(onClick = {

showNotification(context)
}
) {
Text("Show Notification")
}

}
}

fun showNotification(context: Context) {
val channelId = "my_channel_id"

// Create Notification Channel for Android 8+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val name = "Default Channel"
val descriptionText = "General Notifications"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(channelId, name, importance).apply {
description = descriptionText
}

val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
if (ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
val largeIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_launcher_foreground)
// Build the notification
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.android) // Use your icon
.setLargeIcon(largeIcon)
.setContentTitle("Hello Compose")
.setContentText("This is a Jetpack Compose notification!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)

with(NotificationManagerCompat.from(context)) {
notify(100, builder.build())
}
}
}

Simple Notification in Jetpack Compose permission

FINAL RESULT:

Simple Notification in Jetpack Compose permission UI

Simple Notification in Jetpack Compose permission

Advanced Notifications in Android using Jetpack Compose

Notifications are a crucial way to communicate with your app's users, and Jetpack Compose allows seamless integration with the Notification API. While most developers know how to show a simple notification, today we’ll take it to the next level by implementing advanced features like custom layouts, download progress, foreground service indicators, and scheduled reminders using WorkManager.


1. Custom Layouts Using RemoteViews

Want to build a notification that looks like a mini music player? Or show a stylized layout? You can use RemoteViews to create custom notification UIs using XML layouts.

Step 1: Create XML Layout (res/layout/custom_notification.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:padding="10dp"
    android:gravity="center_vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/albumArt"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/ic_music" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Now Playing"
        android:textStyle="bold"
        android:paddingStart="10dp" />
</LinearLayout>

Step 2: Set RemoteView in Notification

val remoteView = RemoteViews(context.packageName, R.layout.custom_notification)
remoteView.setTextViewText(R.id.title, "Playing: Desi Beats")

val notification = NotificationCompat.Builder(context, "channel_id")
    .setSmallIcon(R.drawable.ic_music_note)
    .setCustomContentView(remoteView)
    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
    .build()

NotificationManagerCompat.from(context).notify(101, notification)

2. Foreground Service Notifications (e.g., for Music or Fitness)

When your app runs a long-running task like playing music or tracking steps, Android requires a foreground service with a visible notification.

Step 1: Create the Service

class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = NotificationCompat.Builder(this, "channel_id")
            .setContentTitle("Music Playing")
            .setContentText("Track: Chai & Code")
            .setSmallIcon(R.drawable.ic_music_note)
            .build()

        startForeground(1, notification)
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

Step 2: Start Foreground Service

val serviceIntent = Intent(context, MusicService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)

Don’t forget to add the FOREGROUND_SERVICE permission in your manifest.


3. Progress Bars for Downloads

Let users track download or upload progress with dynamic progress bar notifications.

Example: Show Download Progress

val builder = NotificationCompat.Builder(context, "channel_id")
    .setContentTitle("Downloading file")
    .setSmallIcon(R.drawable.ic_download)
    .setPriority(NotificationCompat.PRIORITY_LOW)

Thread {
    for (progress in 0..100 step 10) {
        builder.setProgress(100, progress, false)
        NotificationManagerCompat.from(context).notify(102, builder.build())
        Thread.sleep(500)
    }
    builder.setContentText("Download complete")
        .setProgress(0, 0, false)
    NotificationManagerCompat.from(context).notify(102, builder.build())
}.start()

This is perfect for file downloads, uploads, or any task with measurable completion.


4. Scheduled or Delayed Notifications using WorkManager

Want to remind users after 10 minutes or send daily quotes? WorkManager is perfect for background scheduling, even when the app is closed.

Step 1: Add Dependency

implementation "androidx.work:work-runtime-ktx:2.9.0"

Step 2: Create a Worker

class ReminderWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val notification = NotificationCompat.Builder(applicationContext, "channel_id")
            .setContentTitle("Daily Tip")
            .setContentText("Jetpack Compose is awesome! - CodingBihar")
            .setSmallIcon(R.drawable.ic_reminder)
            .build()

        NotificationManagerCompat.from(applicationContext).notify(103, notification)
        return Result.success()
    }
}

Step 3: Schedule the Work

val request = OneTimeWorkRequestBuilder<ReminderWorker>()
    .setInitialDelay(10, TimeUnit.MINUTES)
    .build()

WorkManager.getInstance(context).enqueue(request)

You can also use PeriodicWorkRequestBuilder for recurring tasks like daily reminders or motivational quotes.


Conclusion

Advanced notifications help make your Android app more professional and user-friendly. Here’s a quick summary:

  • Custom Layouts: Use RemoteViews to show interactive and branded designs.
  • Foreground Services: Required for long-running tasks like music or fitness.
  • Progress Indicators: Show download/upload progress in real-time.
  • WorkManager: Schedule reminders, alarms, or updates even when the app is closed.

With Jetpack Compose and modern Android APIs, building powerful notification systems is easier than ever.

Custom Layout Notification:

package com.codingbihar.composenotificationdemo

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.widget.RemoteViews
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat

@Composable
fun MessageDemo() {

val context = LocalContext.current
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted ->
val msg = if (isGranted) "Notification permission granted" else "Notification permission denied"
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
)

// Ask permission & create notification channel
LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}

}
Column (Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Button(onClick = {
NotificationHelper.showCustomNotification(context)
}) {
Text("Show Custom Notification")
}
}
}

object NotificationHelper {
private const val CHANNEL_ID = "custom_channel"
private const val CHANNEL_NAME = "Custom Notification"

fun showCustomNotification(context: Context) {
createNotificationChannel(context)
val customView = RemoteViews(context.packageName,
R.layout.custom_notification).apply { setTextViewText(R.id.notification_title, "Hello User!")
setTextViewText(R.id.notification_message, "This is a custom notification.")
setImageViewResource(R.id.notification_image, R.drawable.ic_launcher_foreground)
}

// Intent to open MainActivity when notification clicked
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

if (ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setCustomContentView(customView)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(1001, notification)
}
}

private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}

res/layout/custom_notification.xml:

Loading... 

Foreground Services:

Loading... 

Progress Indicators:

Loading.. 

WorkManager:

Loading... 

Previous Post Next Post

Contact Form