Android Notification Channels — Local Notifications with Actions & Progress
Starting with Android 8 (API 26), notification channels are required. This guide covers channel creation, actions, and progress notifications.
1. Create Notification Channels
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
object NotificationChannels {
const val GENERAL_CHANNEL_ID = "general_notifications"
const val IMPORTANT_CHANNEL_ID = "important_notifications"
const val SILENT_CHANNEL_ID = "silent_notifications"
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// General channel (default sound/vibration)
val generalChannel = NotificationChannel(
GENERAL_CHANNEL_ID,
"General Notifications",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "General app notifications"
}
notificationManager.createNotificationChannel(generalChannel)
// Important channel (high priority, heads-up)
val importantChannel = NotificationChannel(
IMPORTANT_CHANNEL_ID,
"Important Alerts",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Time-sensitive notifications"
}
notificationManager.createNotificationChannel(importantChannel)
// Silent channel (no sound)
val silentChannel = NotificationChannel(
SILENT_CHANNEL_ID,
"Silent Notifications",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Background updates"
}
notificationManager.createNotificationChannel(silentChannel)
}
}
}
2. Basic Notification with PendingIntent
import androidx.core.app.NotificationCompat
import android.app.PendingIntent
import android.content.Intent
fun sendBasicNotification(context: Context, notificationId: Int) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(context, NotificationChannels.GENERAL_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Task Complete")
.setContentText("Your download has finished")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(notificationId, notification)
}
3. Notification with Action Buttons
fun sendNotificationWithActions(context: Context, notificationId: Int) {
// Reply action
val replyIntent = Intent(context, NotificationActionReceiver::class.java).apply {
action = "REPLY_ACTION"
}
val replyPendingIntent = PendingIntent.getBroadcast(
context,
1,
replyIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// Dismiss action
val dismissIntent = Intent(context, NotificationActionReceiver::class.java).apply {
action = "DISMISS_ACTION"
}
val dismissPendingIntent = PendingIntent.getBroadcast(
context,
2,
dismissIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(context, NotificationChannels.IMPORTANT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("New Message")
.setContentText("You have a message from John")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction(R.drawable.ic_reply, "Reply", replyPendingIntent)
.addAction(R.drawable.ic_dismiss, "Dismiss", dismissPendingIntent)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context).notify(notificationId, notification)
}
// Receiver to handle actions
class NotificationActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
"REPLY_ACTION" -> {
println("User replied to notification")
}
"DISMISS_ACTION" -> {
println("User dismissed notification")
}
}
}
}
4. Progress Notification
fun sendProgressNotification(context: Context, notificationId: Int, progress: Int, max: Int = 100) {
val notification = NotificationCompat.Builder(context, NotificationChannels.GENERAL_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Downloading File")
.setContentText("$progress%")
.setProgress(max, progress, false)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.build()
NotificationManagerCompat.from(context).notify(notificationId, notification)
}
// Example: Update progress in a coroutine
fun downloadWithProgress(context: Context) {
viewModelScope.launch {
repeat(100) { progress ->
sendProgressNotification(context, 1, progress)
delay(100)
}
// Complete notification
val completeNotification = NotificationCompat.Builder(context, NotificationChannels.GENERAL_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Download Complete")
.setProgress(0, 0, false)
.setOngoing(false)
.build()
NotificationManagerCompat.from(context).notify(1, completeNotification)
}
}
5. Compose Permission Handling
import android.Manifest
import androidx.compose.runtime.LaunchedEffect
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun NotificationPermissionScreen() {
val postNotificationPermission = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
postNotificationPermission.launchPermissionRequest()
}
}
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
when {
postNotificationPermission.hasPermission -> {
Text("Notifications enabled!")
Button(onClick = {
// Send notification
sendBasicNotification(context, 1)
}) {
Text("Send Notification")
}
}
postNotificationPermission.shouldShowRationale -> {
Text("Notifications are needed to keep you updated")
Button(onClick = { postNotificationPermission.launchPermissionRequest() }) {
Text("Grant Permission")
}
}
else -> {
Text("Permission required for notifications")
}
}
}
}
Key Points:
- Channels must be created before sending notifications (onCreate recommended)
-
IMPORTANCE_HIGHshows heads-up notifications -
IMPORTANCE_LOW/NONEbypasses sound -
FLAG_IMMUTABLEis mandatory for PendingIntent on Android 12+ -
POST_NOTIFICATIONSpermission required for Android 13+
8 Android app templates: Gumroad
Top comments (0)