Android progress notifications in Kotlin
In this tutorial you’ll learn how to create a basic Progress Notification (Indeterminate progress indicator and Fixed-duration progress indicator) for Android using Kotlin. Before we begin, let us first understand the components of a Notification in Android.
Components of a Notification:

- Small Icon – Required, can be set with setSmallIcon().
- Application Name – Provided by the system.
- Time Stamp – Provided by the system but can be overridden.
- Large Icon – Optional, can be set with setLargeIcon().
- Title – Optional, can be set with setContentTitle().
- Text – Optional, can be set with setContentText().
Note : Since the introduction of Android version 8 (Android Oreo), it is now compulsory to categorize all the notifications into categories called Notification Channels. This is for the convenience of users and also developers. The image below shows us a notification channel named ‘Progress Notification’.

Step by Step Implementation:
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language. Choose the API level according to your choice( Here we have chosen API Level 26).
Step 2:Adding Permission for notification
Android 13 (API level 33) and higher need a permission for posting notifications from an app. For this, declare permission in the manifest file. Please manually make sure that the permission for notifications is provided for this app on phone.
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
Step 3: Working with the activity_main.xml file
Go to the activity_main.xml file and refer to the following code. We are just going to add a basic button to trigger the notification.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".MainActivity">
<!-- Button for sending notification-->
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/green"
android:text="Send Notification"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Working with the MainActivity.kt file
Go to the MainActivity.kt file and refer to the following code. Below is the code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail.
MainActivity.kt:
package org.geeksforgeeks.demo
import android.Manifest
import android.annotation.SuppressLint
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.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
class MainActivity : AppCompatActivity() {
private lateinit var button: Button
private lateinit var notificationManager: NotificationManagerCompat
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
notificationManager = NotificationManagerCompat.from(this)
// Create a notification channel (required for Android 8.0 and higher)
createNotificationChannel()
button.setOnClickListener {
// Request runtime permission for notifications on Android 13 and higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
101
)
return@setOnClickListener
}
}
sendNotification() // Trigger the notification
}
}
/**
* Create a notification channel for devices running Android 8.0 or higher.
* A channel groups notifications with similar behavior.
*/
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}
}
/**
* Build and send a notification with a custom layout and action.
*/
@SuppressLint("MissingPermission")
private fun sendNotification() {
val intent = Intent(this, MainActivity::class.java)
.apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE
)
//Sets the maximum progress as 100
val progressMax = 100
// Build the notification
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.gfg_logo) // Notification icon
.setContentTitle("GeeksforGeeks") // Title displayed in the notification
.setContentText("Downloading...") // Text displayed in the notification
.setAutoCancel(true) // Dismiss notification when tapped
.setPriority(NotificationCompat.PRIORITY_HIGH) // Notification priority for better visibility
.setOngoing(true)
.setOnlyAlertOnce(true)
.setProgress(progressMax, 0, true)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
// Display the notification
notificationManager.notify(NOTIFICATION_ID, builder.build())
Thread(Runnable{
SystemClock.sleep(2000)
var progress = 0
while (progress <= progressMax) {
SystemClock.sleep(
1000
)
progress += 7
//Use this to make it a Fixed-duration progress indicator notification
builder.setContentText("$progress%")
.setProgress(progressMax, progress, false)
notificationManager.notify(1, builder.build())
}
builder.setContentText("Download complete")
.setProgress(0, 0, false)
.setOngoing(false)
notificationManager.notify(1, builder.build())
}).start()
}
companion object {
// Unique channel ID for notifications
const val CHANNEL_ID = "i.apps.notifications"
// Unique identifier for the notification
const val NOTIFICATION_ID = 1234
// Description for the notification channel
const val CHANNEL_NAME = "Test notification"
}
}