📱 Device Features · Lesson 37
Notifications & Channels
Anatomy of a notification
| Part | Builder method |
|---|---|
| Small icon (required) | setSmallIcon() |
| Title / text | setContentTitle() / setContentText() |
| Long text | setStyle(BigTextStyle()) |
| Tap action | setContentIntent(pendingIntent) |
| Buttons | addAction() |
Step 1: Create a channel
Kotlin
val channel = NotificationChannel(
"reminders", // id – never change after release
"Reminders", // name shown in Settings
NotificationManager.IMPORTANCE_HIGH // heads-up + sound
).apply { description = "Homework and exam reminders" }
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)Kotlin – StudentDiaryApp.kt (create it once at app start)
package com.codingeasily.studentdiary
import android.app.Application
import com.codingeasily.studentdiary.data.SettingsStore
import com.codingeasily.studentdiary.reminder.NotificationHelper
/**
* Runs once when the app process starts (declared in AndroidManifest.xml).
* We apply the saved light/dark theme and create the notification channel here.
*/
class StudentDiaryApp : Application() {
override fun onCreate() {
super.onCreate()
SettingsStore(this).applyTheme()
NotificationHelper.createChannel(this)
}
}Step 2: Permission (Android 13+)
Declare POST_NOTIFICATIONS in the manifest and request it at runtime on Android 13+ (see the Runtime Permissions lesson). On older versions it is granted automatically.
Step 3: Build and show
Kotlin – simplest notification
val notification = NotificationCompat.Builder(this, "reminders")
.setSmallIcon(R.drawable.ic_notification) // white, transparent background
.setContentTitle("⏰ Maths homework")
.setContentText("Due tomorrow at 9:00 AM")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true) // remove after tap
.build()
NotificationManagerCompat.from(this).notify(101, notification) // 101 = id (same id replaces)Complete helper from Student Diary
Kotlin – reminder/NotificationHelper.kt
package com.codingeasily.studentdiary.reminder
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.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.codingeasily.studentdiary.MainActivity
import com.codingeasily.studentdiary.R
object NotificationHelper {
const val CHANNEL_ID = "reminders"
/** Android 8+ needs a channel before any notification can be shown. */
fun createChannel(context: Context) {
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.channel_reminders),
NotificationManager.IMPORTANCE_HIGH
).apply { description = context.getString(R.string.channel_reminders_desc) }
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
}
fun canNotify(context: Context): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
@SuppressLint("MissingPermission") // we check canNotify() on the first line
fun show(context: Context, noteId: Long, title: String, text: String) {
if (!canNotify(context)) return
// Tapping the notification opens the app
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val pending = PendingIntent.getActivity(
context, noteId.toInt(), intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(text)
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pending)
.setAutoCancel(true)
.build()
try {
NotificationManagerCompat.from(context).notify(noteId.toInt(), notification)
} catch (e: SecurityException) {
// permission was revoked while the app was running – ignore
}
}
}Action buttons and progress
Kotlin
// "Mark done" button handled by a BroadcastReceiver
val doneIntent = Intent(this, MarkDoneReceiver::class.java).putExtra("noteId", note.id)
val donePending = PendingIntent.getBroadcast(this, note.id.toInt(), doneIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(this, "reminders")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Maths homework")
.addAction(R.drawable.ic_check, "Mark done", donePending)
// progress (e.g. uploading notes)
builder.setContentText("Uploading…").setProgress(100, 40, false) // 40%
NotificationManagerCompat.from(this).notify(7, builder.build())From reminder to notification
Common mistakes
- No channel → nothing appears on Android 8+.
- Coloured or photo small icon → shows as a white square; use a simple white vector.
- Missing
FLAG_IMMUTABLE/FLAG_MUTABLE→ crash on Android 12+. - Same notification id for different notes → they replace each other; use the note id.
Summary
- Channel (once) → permission (13+) → NotificationCompat.Builder → notify(id, …).
- PendingIntent for tap and action buttons, with FLAG_IMMUTABLE.
- Keep the code in one helper object and reuse it.
✅ Practice
Add a "Test notification" button in Settings that shows a BigText notification which opens the app when tapped and has a "Mark done" action.
Frequently Asked Questions
Why is my notification not showing on Android 8+?
Every notification needs a channel on Android 8.0 (API 26) and above. Create the channel once (for example in your Application class) before showing notifications.
Can users turn off one type of notification?
Yes. Users control each channel separately in Settings, which is why you should create meaningful channels like "Reminders" and "Offers".
What is a PendingIntent?
A token that lets the system run your Intent later on your behalf — for example opening your Activity when the user taps the notification. Use FLAG_IMMUTABLE on Android 12+.
