
Starting with Android 16, we have a new notification style Notification.ProgressStyle which we can use to create progress-centric notifications.
This article will teach us how to use this new style to provide live updates to end-users.
Important use-cases that we can implement with this are the following
- Rideshare 🚕
- Food Delivery 🚚 🥘
- Navigation 🧭, etc.
ProgressStyle is quite customizable; It includes the following components that we can use to provide a live updates experience to end-users
- Progress bar
- Segments — A segment of the progress bar, which defines its length and color 🎨
- Points — A point within the progress bar, defining its position and color 🎨
- Tracker icon 🟢
- Start 🎬 and end icon 🔚 for the progress bar

Guidelines for icons

Implementation
- In the app’s
build.gradle set the compile SDK to Android 16
android {
// Android 16
compileSdk = 36
defaultConfig {
minSdk = 29
// Android 16
targetSdk = 36
}
}
Make sure that the app has permission to post promoted notifications
- We can use the
canPostPromotedNotifications method ofNotificationManager to check if our application has valid access or not.
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun checkInitialization(context: Context) {
val canPostLiveUpdates = notificationManager.canPostPromotedNotifications()
}
Redirect the user to the settings to update their permissions
- If the last step’s 👆 result is
false We can redirect the user to the settings to update the settings, and then go from there.
val intent = Intent(ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT).setData(
"package:${appContext.packageName}".toUri()),
)
(context as Activity).startActivityForResult(intent, PERMISSION_REQUEST_CODE)
Verify if the notification itself is promotable
- We can use the
hasPromotableCharacteristics ofNotification to ensure the notification is valid for live update.
val isPromotable = notification.hasPromotableCharacteristics()
We will create a live update experience for the food delivery use case 🥘
- Let’s create a notification channel
IMPORTANCE_MIN = 1does not qualify a notification as a live update
object LiveUpdatesNotificationManager {
private lateinit var notificationManager: NotificationManager
private lateinit var appContext: Context
const val CHANNEL_ID = "live_updates_16_channel_id"
private const val CHANNEL_NAME = "live_updates_16_channel_name"
private const val NOTIFICATION_ID = 4321
fun createChannel(context: Context, notifManager: NotificationManager) {
notificationManager = notifManager
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT)
appContext = context
notificationManager.createNotificationChannel(channel)
}
- For our use case, we will have the following five states
- Order placed or confirmed 👍
- Preparing 🧑🍳
- EnRoute 🛣️
- Arriving 🚗
- Delivered ✅
To get started, let’s start with the base notification.
fun buildBaseNotification(appContext: Context): Notification.Builder {
return Notification.Builder(appContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setOngoing(true)
.setColorized(true)
.setColor(Color.GRAY)
}
We need to set the style to ProgressStyle, that can be a live update
So let’s create a Progress style for each case
- Using the previous step’s👆 base notification, we will build upon it
Order placed or confirmed — Step 1️⃣
fun buildBaseNotification(): Notification.Builder {
return buildBaseNotification(appContext, INITIALIZING)
.setContentTitle("You order is being placed")
.setContentText("Confirming with Spicy7...")
}
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildBaseProgressStyle(): ProgressStyle {
val progressStyle = ProgressStyle()
.setProgressPoints(
listOf(
ProgressStyle.Point(25).setColor(Color.MAGENTA),
ProgressStyle.Point(50).setColor(Color.RED),
ProgressStyle.Point(75).setColor(Color.GREEN)
)
).setProgressSegments(
listOf(
ProgressStyle.Segment(25).setColor(Color.BLUE),
ProgressStyle.Segment(25).setColor(Color.MAGENTA),
ProgressStyle.Segment(25).setColor(Color.LTGRAY),
ProgressStyle.Segment(25).setColor(Color.RED)
)
)
return progressStyle
}
So here we started with 3 points and four segments.
Result

Preparing 🧑🍳 — Step 2️⃣
- We have updated the progress to 25 and added the large icon to the existing notification.
- Title and text have been updated
.setContentTitle(“Your order is being prepared”)
.setContentText(“Next step will be delivery”) - We will keep three
points and foursegments.
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildNotification(): Notification.Builder {
return buildBaseNotification(appContext, FOOD_PREPARATION)
.setContentTitle("Your order is being prepared")
.setContentText("Next step will be delivery")
.setLargeIcon(
IconCompat.createWithResource(
appContext, R.drawable.ic_notif_large,
).toIcon(appContext),
)
.setStyle(buildBaseProgressStyle().setProgress(25))
}
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildBaseProgressStyle(): ProgressStyle {
// same like step 1.
}
Result

EnRoute 🛣️ — Step 3️⃣
- Here, we set the progress to 50 and set the progress tracker icon 🥯
- Title and text have been updated
.setContentTitle(“Your order is on its way”)
.setContentText(“Enroute to destination”) - We show one
points(25), and foursegments
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildNotification(): Notification.Builder {
return buildBaseNotification(appContext, FOOD_ENROUTE)
.setContentTitle("Your order is on its way")
.setContentText("Enroute to destination")
.setStyle(
buildBaseProgressStyle()
.setProgressTrackerIcon(
IconCompat.createWithResource(
appContext, R.drawable.shopping_bag,
).toIcon(appContext),
)
.setProgress(50),
)
.setLargeIcon(
IconCompat.createWithResource(
appContext, R.drawable.ic_notif_large,
).toIcon(appContext),
)
}
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildBaseProgressStyle(): ProgressStyle {
val progressStyle = ProgressStyle()
.setProgressPoints(
listOf(
ProgressStyle.Point(25).setColor(Color.MAGENTA)
)
).setProgressSegments(
listOf(
ProgressStyle.Segment(25).setColor(Color.BLUE),
ProgressStyle.Segment(25).setColor(Color.MAGENTA),
ProgressStyle.Segment(25).setColor(Color.LTGRAY),
ProgressStyle.Segment(25).setColor(Color.RED)
)
)
return progressStyle
}
Result

Arriving 🚗 — Step 4️⃣
- Here, we set the progress to 75 and set the progress tracker icon 🥯
- Title and text have been updated
.setContentTitle(“Your order is arriving...”)
.setContentText(“Enjoy.”) - We show two
points(25, 50), and foursegments
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildNotification(): Notification.Builder {
return buildBaseNotification(appContext, FOOD_ARRIVING)
.setContentTitle("Your order is arriving...")
.setContentText("Enjoy 😋")
.setStyle(
buildBaseProgressStyle(FOOD_ARRIVING)
.setProgressTrackerIcon(
IconCompat.createWithResource(
appContext, R.drawable.local_shipping,
).toIcon(appContext),
)
.setProgress(75),
)
.setLargeIcon(
IconCompat.createWithResource(
appContext, R.drawable.ic_notif_large,
).toIcon(appContext),
)
}
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildBaseProgressStyle(): ProgressStyle {
val progressStyle = ProgressStyle()
.setProgressPoints(
listOf(
ProgressStyle.Point(25).setColor(Color.MAGENTA),
ProgressStyle.Point(50).setColor(Color.RED),
)
).setProgressSegments(
listOf(
ProgressStyle.Segment(25).setColor(Color.BLUE),
ProgressStyle.Segment(25).setColor(Color.MAGENTA),
ProgressStyle.Segment(25).setColor(Color.LTGRAY),
ProgressStyle.Segment(25).setColor(Color.RED)
)
)
return progressStyle
}sasa
Result

Delivered ✅ — Step 5️⃣
- Here, we set the progress to 100 and set the progress tracker icon ✅
- Title and text have been updated
.setContentTitle(“Your order is complete.”)
.setContentText(“Thank you for using our app.”) - We show three
points(25, 50, 75), and foursegments
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildNotification(): Notification.Builder {
return buildBaseNotification(appContext, ORDER_COMPLETE)
.setContentTitle("Your order is complete.")
.setContentText("Thank you for using our app.")
.setStyle(
buildBaseProgressStyle(ORDER_COMPLETE)
.setProgressTrackerIcon(
IconCompat.createWithResource(
appContext, R.drawable.check_box,
).toIcon(appContext),
)
.setProgress(100),
)
.setLargeIcon(
IconCompat.createWithResource(
appContext, R.drawable.ic_notif_large,
).toIcon(appContext),
)
}
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun buildBaseProgressStyle(): ProgressStyle {
val progressStyle = ProgressStyle()
.setProgressPoints(
listOf(
ProgressStyle.Point(25).setColor(Color.MAGENTA),
ProgressStyle.Point(50).setColor(Color.RED),
ProgressStyle.Point(75).setColor(Color.LTGRAY)
)
)
return progressStyle
}
Result

A few important points
- Points at the start and end will not show in the
progress bar DEFAULT_PROGRESS_MAX is 100MAX_PROGRESS_POINT_LIMIT is 4Progress bar’s max value is calculated based on the segments


