Irbisa · Cheatsheet
Notifications & Push
NotificationCompat, channels, POST_NOTIFICATIONS, FCM, data vs notification messages, deep links
What is a notification channel, and why does raising its importance in code later change nothing?
class App : Application() { override fun onCreate() { super.onCreate() // Idempotent: safe on every launch, must run before the first notify() NotificationManagerCompat.from(this).createNotificationChannel( NotificationChannelCompat.Builder("delivery_v2", NotificationManagerCompat.IMPORTANCE_HIGH)…What is the minimum a
NotificationCompat.Builderneeds to post a working notification?val tap = PendingIntent.getActivity( ctx, 0, Intent(Intent.ACTION_VIEW, "myapp://orders/42".toUri(), ctx, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE)…Which notification styles does
NotificationCompatoffer, and which part of the notification does a style change?val release = NotificationCompat.Builder(ctx, "news") .setSmallIcon(R.drawable.ic_stat_news) .setContentTitle("Release 4.2 is out") .setContentText("Offline sync, faster search, 40 fixes") // collapsed row .setStyle(NotificationCompat.BigTextStyle().bigText(fullChangelog)) // expanded only .build()…How do you update or remove a notification that is already posted?
val nm = NotificationManagerCompat.from(ctx)val base = NotificationCompat.Builder(ctx, "orders") .setSmallIcon(R.drawable.ic_stat_order) .setContentTitle("Order #42") // Same id: updates the row in place, quietly…What changed with
POST_NOTIFICATIONSin Android 13, and what do you do when the user declines?// AndroidManifest.xml: <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> @Composablefun PriceAlertButton(onEnabled: () -> Unit) { val ctx = LocalContext.current val launcher = rememberLauncherForActivityResult(…What decides how loud a notification is: channel importance,
setPriority, or the FCM message priority?The notification's content intent points at this receiver. On Android 12+ the screen never opens. Why, and what do you do instead?
// setContentIntent(PendingIntent.getBroadcast(ctx, 0, openIntent, FLAG_IMMUTABLE))class OpenReceiver : BroadcastReceiver() { override fun onReceive(ctx: Context, intent: Intent) { val route = intent.getStringExtra("route") analytics.log("notification_open", route) ctx.startActivity( Intent(ctx, MainActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra("route", route) ) }}Find the bug: with two chat notifications in the shade, tapping either one opens the chat that messaged last.
fun showChatNotification(ctx: Context, chat: Chat, text: String) { val open = Intent(ctx, MainActivity::class.java) .putExtra("chat_id", chat.id) val pi = PendingIntent.getActivity( ctx, 0, open, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) val notification = NotificationCompat.Builder(ctx, CHANNEL_CHATS) .setSmallIcon(R.drawable.ic_stat_chat) .setContentTitle(chat.title) .setContentText(text) .setContentIntent(pi) .setAutoCancel(true) .build() NotificationManagerCompat.from(ctx).notify(chat.id, notification)}How do you group notifications under a summary so that ten results produce one sound instead of ten?
private const val GROUP_SYNC = "sync_results"private const val SUMMARY_ID = 0 fun postSyncResults(ctx: Context, files: List<SyncedFile>) { val nm = NotificationManagerCompat.from(ctx) files.forEach { file ->…This download updates a progress notification on every chunk. What does the user hear, and why does the bar stay full while the "Done" text never appears?
suspend fun download(ctx: Context, file: RemoteFile) { val nm = NotificationManagerCompat.from(ctx) val builder = NotificationCompat.Builder(ctx, "downloads") // IMPORTANCE_DEFAULT .setSmallIcon(R.drawable.ic_stat_download) .setContentTitle(file.name) file.chunks().collectIndexed { i, chunk -> sink.write(chunk) builder.setProgress(100, (i + 1) * 100 / file.chunkCount, false) nm.notify(DOWNLOAD_ID, builder.build()) } builder.setContentText("Done").setProgress(0, 0, false) nm.notify(DOWNLOAD_ID, builder.build())}In this direct-reply setup
getResultsFromIntentreturnsnull, and once that is fixed the reply box keeps spinning. What is wrong?val replyPi = PendingIntent.getBroadcast( ctx, 0, Intent(ctx, ReplyReceiver::class.java), PendingIntent.FLAG_IMMUTABLE)val action = NotificationCompat.Action.Builder(R.drawable.ic_send, "Reply", replyPi) .addRemoteInput(RemoteInput.Builder(KEY_REPLY).setLabel("Message").build()) .build() class ReplyReceiver : BroadcastReceiver() { override fun onReceive(ctx: Context, intent: Intent) { val text = RemoteInput.getResultsFromIntent(intent) ?.getCharSequence(KEY_REPLY) ?: return SendReplyWorker.enqueue(ctx, text.toString()) }}What does a chat notification need to appear in the Conversations section and support bubbles?
val sender = Person.Builder().setName("Dana").setKey("user:88").build()val shortcutId = "chat:88" // 1. Long-lived shortcut: without it there is no Conversations sectionShortcutManagerCompat.pushDynamicShortcut( ctx,…What is the difference between FCM notification messages and data messages, and when does
onMessageReceivedactually run?class AppMessagingService : FirebaseMessagingService() { // Data-only: runs in every app state. With a "notification" block: // runs only while the app is in the foreground. override fun onMessageReceived(message: RemoteMessage) { val convId = message.data["conversation_id"] ?: return repository.insert(message.data)…How do you manage FCM registration tokens over their lifetime, on the client and on the server?
class AppMessagingService : FirebaseMessagingService() { // Install, data clear, restore, rotation; often before anyone signed in override fun onNewToken(token: String) { TokenStore.savePending(this, token) TokenUploadWorker.enqueue(this) // retryable: the network may be down }…How do you keep a one-time code off the lock screen, and what do the user and the admin control instead of you?
fun otpNotification(ctx: Context, code: String, tap: PendingIntent): Notification { val redacted = NotificationCompat.Builder(ctx, "security") .setSmallIcon(R.drawable.ic_lock) .setContentTitle("New sign-in code") // says what it is, never the code .setContentIntent(tap) // same tap target .build()…What is a full-screen intent, and what changed with
USE_FULL_SCREEN_INTENTin Android 14?fun ensureFullScreenIntent(activity: Activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return val nm = activity.getSystemService(NotificationManager::class.java) if (nm.canUseFullScreenIntent()) return activity.startActivity( Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, "package:${activity.packageName}".toUri())…A chat push arrives while the user is reading that exact conversation. What should happen, and where in the architecture is that decided?
How do FCM's
collapse_key,ttland message priority affect delivery, and how do you choose them per message type?// Server, Firebase Admin SDK: one shape per message typefun driverArrived(token: String, rideId: String): Message = Message.builder() .setToken(token) .putData("ride_id", rideId) .setAndroidConfig(AndroidConfig.builder() .setPriority(AndroidConfig.Priority.HIGH) // wake it out of Doze…Users on some OEM phones say notifications never arrive, and you cannot reproduce it. How do you find where they are lost?