Sitemap
The PickMe Engineering Blog

At PickMe our purpose is to create joyful mobility experiences for our consumers. In this journey our technology leads the way and the solutions that we build help millions of customers and thousands of service partners to efficiently engage in a single platform.

Guide to Show Your App Notifications on Android Devices

6 min readSep 16, 2026

--

A practical guide to creating Android notifications with Kotlin and Jetpack Compose

Press enter or click to view image in full size

Notifications are an important part of modern Android applications. They help users stay informed about reminders, messages, delivery updates, background tasks, and other timely events — even when the app is not open.

However, showing a notification is not just about calling one method. Modern Android versions require notification channels, runtime permissions, and carefully designed user interactions.

This guide explains how to create and display notifications using Kotlin, AndroidX, and Jetpack Compose.

How Android Notifications Work

Android notifications can appear in several places:

  • The status bar
  • The notification drawer
  • Heads-up popups
  • The lock screen
  • Badges on the app icon
  • Paired Wear OS devices

Users can tap a notification to open your app or interact with it directly using action buttons. Android uses system templates to keep notifications visually consistent across different devices.

Step 1: Add the Notification Permission

Starting with Android 13, applications need the POST_NOTIFICATIONS runtime permission to send most notifications.

First, add the permission to AndroidManifest.xml:

<manifest ...>

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

<application ...>
...
</application>

</manifest>

The permission is only required on Android 13 and higher. On older Android versions, this permission request is ignored.

Android 13 also turns notifications off by default for newly installed applications. Therefore, your app should request permission before trying to show a notification.

Step 2: Request Permission in Jetpack Compose

In a Compose application, rememberLauncherForActivityResult() can be used to request notification permission.

@Composable
fun NotificationPermissionButton() {
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// Permission granted
} else {
// Permission denied
}
}

Button(
onClick = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}
}
) {
Text("Enable Notifications")
}
}

A good practice is to request permission in context instead of immediately displaying the system dialog when the app starts.

For example, you could request permission when:

  • The user enables delivery updates
  • The user taps an alert bell
  • The user places an order
  • The user follows a conversation

Explaining the value of notifications usually creates a better user experience than showing an unexpected permission dialog.

Step 3: Create a Notification Channel

Starting with Android 8.0, every notification must belong to a notification channel.

A channel allows users to control the behavior of a particular type of notification. For example, a delivery application could have separate channels for:

  • Order updates
  • Promotions
  • Driver messages
private const val CHANNEL_ID = "order_updates"

fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Order Updates",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Notifications about order status changes"
}

val notificationManager =
context.getSystemService(NotificationManager::class.java)

notificationManager.createNotificationChannel(channel)
}
}

Create the channel when the application starts, such as inside your Application class:

class MyApplication : Application() {

override fun onCreate() {
super.onCreate()

createNotificationChannel(this)
}
}

Creating an existing channel again does nothing, so it is safe to call this method repeatedly.

One important limitation is that the channel’s importance and sound behavior cannot be changed programmatically after the channel is created. The user has final control through system settings. Notification channels

Step 4: Build the Notification

Use NotificationCompat.Builder to define the notification content.

fun buildNotification(context: Context): Notification {
return NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Order update")
.setContentText("Your order is on the way")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
.build()
}

The small icon is required. It should be a notification-friendly icon, usually a white monochrome icon with a transparent background.

Avoid using your full-color application icon as the small notification icon because Android applies its own styling to it.

Step 5: Add a Tap Action

A notification should normally respond when the user taps it.

Create an explicit Intent that opens the relevant screen in your application:

fun buildNotification(context: Context): Notification {
val intent = Intent(context, OrderDetailsActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
}

val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)

return NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Order update")
.setContentText("Your order is on the way")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
}

setAutoCancel(true) removes the notification after the user taps it.

The destination should be relevant to the notification. If the notification is about an order, open the order details screen instead of opening the application home screen.

Step 6: Display the Notification

Finally, use NotificationManagerCompat to display the notification.

fun showOrderNotification(context: Context) {
val notification = buildNotification(context)

with(NotificationManagerCompat.from(context)) {
if (
ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}

notify(ORDER_NOTIFICATION_ID, notification)
}
}

private const val ORDER_NOTIFICATION_ID = 1001

The notification ID should be unique for each notification.

Keep the ID if you want to update or remove the notification later.

Get Pavithra Chamod J’s stories in your inbox

Join Medium for free to get updates from this writer.

For example, calling notify() again with the same ID updates the existing notification:

NotificationManagerCompat
.from(context)
.notify(ORDER_NOTIFICATION_ID, updatedNotification)

This is useful for progress updates such as:

  • Download progress
  • Ride status
  • Delivery status
  • Upload progress

Step 7: Trigger the Notification from Compose

A Compose button can trigger the notification:

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

Button(
onClick = {
showOrderNotification(context)
}
) {
Text("Show Notification")
}
}

In a real application, notifications are usually triggered from a repository, worker, service, or push notification handler rather than directly from the UI.

The UI can still provide a manual test button during development.

Expandable Notifications

Notification text is truncated by default. If you need to display more content, use BigTextStyle.

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Delivery update")
.setContentText("Your driver has picked up the order")
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(
"Your driver has picked up the order and is " +
"currently travelling to your delivery location."
)
)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()

Android also supports other notification styles, including:

  • BigPictureStyle
  • InboxStyle
  • MessagingStyle
  • Media-style notifications

Choose the style based on the type of content instead of forcing every notification into the same format.

Adding Action Buttons

Action buttons let users complete common tasks directly from a notification.

val snoozeIntent = Intent(
context,
NotificationActionReceiver::class.java
).apply {
action = "ACTION_SNOOZE"
}

val snoozePendingIntent = PendingIntent.getBroadcast(
context,
0,
snoozeIntent,
PendingIntent.FLAG_IMMUTABLE
)

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Reminder")
.setContentText("Your appointment starts soon")
.addAction(
R.drawable.ic_snooze,
"Snooze",
snoozePendingIntent
)
.build()

A notification can provide up to three action buttons. These actions should be short, useful, and different from the default tap action.

Direct Reply Notifications

Messaging applications can allow users to reply without opening the app.

This is implemented using RemoteInput:

val remoteInput = RemoteInput.Builder("reply_text")
.setLabel("Type a reply")
.build()

val replyAction = NotificationCompat.Action.Builder(
R.drawable.ic_reply,
"Reply",
replyPendingIntent
)
.addRemoteInput(remoteInput)
.build()

The reply text can then be retrieved inside a BroadcastReceiver:

override fun onReceive(context: Context, intent: Intent) {
val replyText = RemoteInput
.getResultsFromIntent(intent)
?.getCharSequence("reply_text")

// Process the reply
}

For chat applications, use NotificationCompat.MessagingStyle so Android can present the conversation naturally.

Common Notification Mistakes

1. Forgetting the notification channel

On Android 8.0 and higher, a notification without a valid channel does not appear.

2. Forgetting the Android 13 permission

If POST_NOTIFICATIONS has not been granted, notify() will not display the notification.

3. Using the wrong icon

The small icon should be a simple monochrome notification icon, not a full application logo.

4. Sending too many notifications

Avoid creating a new notification for every small update. Update an existing notification or group related notifications together.

5. Choosing high importance for everything

High-importance notifications can make sounds and appear as heads-up notifications. Use them only when the information is genuinely urgent.

6. Ignoring user settings

Users can disable individual channels, change their importance, mute sounds, or disable notifications entirely. Your app should not assume that a notification will always be delivered visibly.

Final Thoughts

Android notifications are more than small messages displayed outside the application. They are a communication channel between your app and the user.

A reliable implementation should:

  • Declare POST_NOTIFICATIONS
  • Request permission at the right moment
  • Create notification channels
  • Use a valid small icon
  • Add a meaningful tap action
  • Reuse notification IDs for updates
  • Respect the user’s notification preferences
  • Avoid unnecessary interruptions

Once these fundamentals are in place, you can extend your notifications with expandable content, action buttons, direct replies, progress updates, messaging styles, and wearable-specific features.

The goal is not to send more notifications. The goal is to send useful notifications at the right time.

Happy coding!

— Pavithra Chamod J

Press enter or click to view image in full size
Photo by Andrey Matveev on Unsplash

References

#Android #Kotlin #JetpackCompose #AndroidNotifications #AndroidDev #SoftwareEngineering #CleanCode #MobileDevelopment

--

--

Pavithra Chamod J
Pavithra Chamod J

Written by Pavithra Chamod J

Mobile Application Developer Intern - Undergraduate in Computer Science

The PickMe Engineering Blog
The PickMe Engineering Blog

Published in The PickMe Engineering Blog

At PickMe our purpose is to create joyful mobility experiences for our consumers. In this journey our technology leads the way and the solutions that we build help millions of customers and thousands of service partners to efficiently engage in a single platform.