Notifikasi adalah cara utama aplikasi Android mengomunikasikan peristiwa ke pengguna di luar UI. Sejak Android 8.0 (API 26), setiap notifikasi wajib berasosiasi dengan notification channel — kategori yang dapat pengguna atur secara terpisah. Bab ini menjelaskan cara membuat channel, menampilkan notifikasi dasar, menambahkan action button lewat PendingIntent, dan menerbitkan notifikasi foreground service yang wajib untuk Service berjalan lama.
Notification Channels #
Channel adalah kategori yang dikenali pengguna. Pengguna dapat mematikan channel “Promosi” tanpa mematikan “Pesanan”. Channel dibuat sekali per aplikasi dan tidak dapat diubah setelah dibuat — yang dapat diubah hanya nama dan deskripsi, bukan importance.
val channel = NotificationChannel(
"pesan",
"Pesan Baru",
NotificationManager.IMPORTANCE_HIGH // 1=LOW, 2=DEFAULT, 3=HIGH
).apply {
description = "Notifikasi pesan masuk"
enableVibration(true)
enableLights(true)
}
val nm = context.getSystemService(NotificationManager::class.java)
nm.createNotificationChannel(channel)Tabel importance menentukan perilaku default channel:
| Importance | Suara | Popup heads-up | Status bar |
|---|---|---|---|
IMPORTANCE_HIGH | Ya | Ya | Ya |
IMPORTANCE_DEFAULT | Ya | Tidak | Ya |
IMPORTANCE_LOW | Tidak | Tidak | Ya |
IMPORTANCE_MIN | Tidak | Tidak | Tidak (hanya shade) |
Notification Dasar dengan PendingIntent #
Notifikasi dasar terdiri dari judul, teks, icon kecil, dan PendingIntent yang dipanggil saat pengguna mengetuk notifikasi. PendingIntent membungkus Intent sehingga sistem dapat mengeksekusinya atas nama aplikasi kita, bahkan jika aplikasi tidak berjalan.
val intent = Intent(context, TugasDetailActivity::class.java).apply {
putExtra("TUGAS_ID", 42)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(context, "tugas")
.setSmallIcon(R.drawable.ic_notif)
.setContentTitle("Pengingat: Belajar Kotlin")
.setContentText("Tugas id=42 belum selesai")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context).notify(1001, notification)setAutoCancel(true) membuat notifikasi hilang saat diketuk. FLAG_IMMUTABLE wajib di Android 12+ untuk alasan keamanan.
Action Button #
Action button memungkinkan pengguna merespons notifikasi tanpa membuka aplikasi. Setiap action memerlukan PendingIntent sendiri (biasanya ke BroadcastReceiver).
val selesaiIntent = Intent(context, TugasActionReceiver::class.java).apply {
action = "ACTION_SELESAI"
putExtra("TUGAS_ID", 42)
}
val selesaiPending = PendingIntent.getBroadcast(
context, 0, selesaiIntent, PendingIntent.FLAG_IMMUTABLE
)
val notif = NotificationCompat.Builder(context, "tugas")
.setSmallIcon(R.drawable.ic_notif)
.setContentTitle("Pengingat Tugas")
.setContentText("Belajar Kotlin")
.addAction(R.drawable.ic_check, "Tandai Selesai", selesaiPending)
.build()Foreground Service Notification #
Service yang berjalan di foreground (untuk tugas yang harus tetap aktif saat pengguna keluar, mis. pemutar musik atau tracking GPS) wajib menampilkan notifikasi persistent. Tanpa notifikasi, sistem akan menghentikan Service.
class PlayerService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notif = NotificationCompat.Builder(this, "musik")
.setContentTitle("Memutar musik")
.setContentText("Lagu: Belajar Kotlin")
.setSmallIcon(R.drawable.ic_play)
.build()
startForeground(NOTIFICATION_ID, notif) // wajib untuk foreground service
return START_STICKY
}
}Permission FOREGROUND_SERVICE wajib dideklarasikan di manifest. Mulai Android 14 (API 34), setiap tipe foreground service (mis. mediaPlayback, location, dataSync) butuh permission spesifik.
Simulasi NotificationManager #
Cuplikan berikut memodelkan channel, notifikasi, dan PendingIntent sebagai struktur data ringkas. NotificationManager memvalidasi bahwa channel sudah dibuat sebelum notifikasi diterbitkan:
// notifications.kt
// Mensimulasikan notification channels (Android 8+), NotificationManager,
// PendingIntent, dan foreground service sebagai struktur data ringkas.
data class NotificationChannel(
val id: String,
val name: String,
val importance: Int, // 1=LOW, 2=DEFAULT, 3=HIGH
val description: String
)
data class Notification(
val id: Int,
val channelId: String,
val title: String,
val text: String,
val priority: Int,
val pendingIntent: PendingIntent? = null,
val actions: List<NotificationAction> = emptyList()
)
data class PendingIntent(val target: String, val extras: Map<String, Any?> = emptyMap())
data class NotificationAction(val title: String, val intent: PendingIntent)
class NotificationManager {
private val channels = mutableMapOf<String, NotificationChannel>()
private val notifications = mutableMapOf<Int, Notification>()
fun createChannel(channel: NotificationChannel) {
require(channels.put(channel.id, channel) == null) {
"Channel ${channel.id} sudah ada"
}
println(" [Channel] dibuat: ${channel.id} (${channel.name}) importance=${channel.importance}")
}
fun notify(notification: Notification) {
require(channels.containsKey(notification.channelId)) {
"Channel ${notification.channelId} belum terdaftar"
}
notifications[notification.id] = notification
println(" [Notify] id=${notification.id} channel=${notification.channelId}")
println(" title=${notification.title} text=${notification.text}")
notification.actions.forEach { println(" action: ${it.title} -> ${it.intent.target}") }
}
fun cancel(id: Int) {
notifications.remove(id)
println(" [Cancel] id=$id")
}
fun activeCount(): Int = notifications.size
fun getChannel(id: String): NotificationChannel? = channels[id]
}
fun main() {
val nm = NotificationManager()
println("=== Buat notification channels (Android 8+ wajib) ===")
nm.createChannel(NotificationChannel("pesan", "Pesan Baru", 3, "Notifikasi pesan masuk"))
nm.createChannel(NotificationChannel("tugas", "Pengingat Tugas", 2, "Notifikasi pengingat tugas"))
nm.createChannel(NotificationChannel("download", "Status Download", 1, "Progress download"))
println("\n=== Notify dengan action button ===")
val bukaIntent = PendingIntent("MainActivity", mapOf("tugas_id" to 42))
val tandaiSelesai = PendingIntent("TugasActionReceiver", mapOf("action" to "selesai", "tugas_id" to 42))
nm.notify(Notification(
id = 1001,
channelId = "tugas",
title = "Pengingat: Belajar Kotlin",
text = "Tugas id=42 belum selesai",
priority = 2,
pendingIntent = bukaIntent,
actions = listOf(
NotificationAction("Tandai Selesai", tandaiSelesai),
NotificationAction("Tunda 1 Jam", PendingIntent("TugasActionReceiver", mapOf("action" to "tunda")))
)
))
println("\n=== Foreground service notification (wajib Android 8+) ===")
nm.notify(Notification(
id = 2001,
channelId = "download",
title = "Mengunduh file...",
text = "Progress: 45%",
priority = 1,
pendingIntent = PendingIntent("DownloadActivity")
))
println("\n=== Status akhir ===")
println("Notifikasi aktif: ${nm.activeCount()}")
nm.cancel(2001)
println("Setelah cancel 2001: ${nm.activeCount()} aktif")
}Output yang Dihasilkan:
=== Buat notification channels (Android 8+ wajib) ===
[Channel] dibuat: pesan (Pesan Baru) importance=3
[Channel] dibuat: tugas (Pengingat Tugas) importance=2
[Channel] dibuat: download (Status Download) importance=1
=== Notify dengan action button ===
[Notify] id=1001 channel=tugas
title=Pengingat: Belajar Kotlin text=Tugas id=42 belum selesai
action: Tandai Selesai -> TugasActionReceiver
action: Tunda 1 Jam -> TugasActionReceiver
=== Foreground service notification (wajib Android 8+) ===
[Notify] id=2001 channel=download
title=Mengunduh file... text=Progress: 45%
=== Status akhir ===
Notifikasi aktif: 2
[Cancel] id=2001
Setelah cancel 2001: 1 aktifPerhatikan bahwa NotificationManager.notify() memvalidasi channel terlebih dahulu. Tanpa channel, panggilan akan gagal — persis seperti runtime Android yang menolak notifikasi tanpa channel.
Referensi Lanjutan #
- Notifications — panduan resmi Notifications API Android untuk menampilkan notifikasi pengguna
Selanjutnya #
Notifikasi adalah satu aspek UX. Bab Android Security akan membahas aspek keamanan: EncryptedSharedPreferences, EncryptedFile, network security config, dan ProGuard/R8 untuk obfuscation.