Інтеграція push-сповіщень через Firebase Cloud Messaging (FCM)
FCM — стандарт push-сповіщень для Android. Але «додати Firebase» та «правильно інтегрувати FCM» — різні речі. Типічні проблеми: сповіщення приходять, коли додаток открит, та пропадають у background. Або приходять, але без кастомного звуку й іконки. Або токен оновлюється, а сервер про це не знає.
Типи повідомлень FCM: notification vs data
Це розмежування критично й часто розуміється неправильно.
Notification message (display message): FCM SDK показує сповіщення автоматично, коли додаток у background/terminated. Кастомізація обмежена — тільки title, body, icon, color. onMessageReceived у foreground викликається, у background — ні.
Data message: payload містить тільки data без блоку notification. FCM не показує ничого автоматично. onMessageReceived викликається завжди — й у foreground, й у background, й у terminated (через FirebaseMessagingService). Повний контроль над відображенням.
Для більшості реальних додатків правильний вибір — data-only з ручним побудуванням сповіщення в onMessageReceived.
Налаштування сервісу
class PushMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
// Надсилаємо token на сервер
ApiClient.registerFcmToken(token)
}
override fun onMessageReceived(message: RemoteMessage) {
val title = message.data["title"] ?: return
val body = message.data["body"] ?: return
showNotification(title, body, message.data)
}
private fun showNotification(title: String, body: String, data: Map<String, String>) {
val channelId = "default_channel"
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra("payload", data.toString())
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification) // Важливо: біла іконка на прозорому фоні
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
NotificationManagerCompat.from(this).notify(System.currentTimeMillis().toInt(), notification)
}
}
ic_notification — біла монохромна іконка 24dp. Якщо передати кольорову — Android 5+ покаже сірий квадрат замість іконки. Це найчастіша візуальна помилка.
Notification Channels (Android 8+)
Без каналу сповіщення не покажеться на Android 8+. Канал створюється один раз при запуску:
fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"default_channel",
"Основні сповіщення",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Сообщения и обновления"
enableLights(true)
lightColor = Color.BLUE
enableVibration(true)
}
context.getSystemService(NotificationManager::class.java)
?.createNotificationChannel(channel)
}
}
IMPORTANCE_HIGH — сповіщення зі звуком та heads-up. IMPORTANCE_DEFAULT — без heads-up. Вибір залежить від типу сповіщень.
Дозвіл POST_NOTIFICATIONS (Android 13+)
// Android 13+ потребує явного дозволу
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
REQUEST_CODE_NOTIFICATIONS
)
}
Запрошуємо в потрібний момент контексту — не при першому запуску додатку, а коли користувач включає сповіщення в налаштуваннях.
Життєвий цикл токена
onNewToken викликається при першій реєстрації та при оновленні токена (переустановка, очистка даних, оновлення Google Play Services). Токен потрібно завжди зберігати на сервері. При відправці на застарілий токен FCM повертає INVALID_REGISTRATION або NOT_REGISTERED — сервер повинен видаляти такі токени.
Отримати поточний токен вручну:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
// Надсилаємо на сервер при кожному запуску (для надійності)
}
}
Теги та підписки на топіки
Для широковещательних сповіщень (всім користувачам або групі) — FCM Topics:
FirebaseMessaging.getInstance().subscribeToTopic("news")
.addOnCompleteListener { task ->
if (task.isSuccessful) Log.d("FCM", "Subscribed to news topic")
}
Відправка на топік на сервері: "to": "/topics/news". Доставка впродовж хвилин, не гарантовано один раз.
Що входить у роботу
- Підключення Firebase SDK,
google-services.json -
FirebaseMessagingServiceз data-message обробкою - Notification Channels з правильними параметрами
- Біла іконка сповіщення
- Запит дозволу
POST_NOTIFICATIONSна Android 13+ - Lifecycle токена з оновленням на сервері
- Обробка tap на сповіщення: навігація до потрібного екрана
- Topics для групових сповіщень
Терміни
Базова інтеграція FCM з alert-сповіщеннями: 1 день. З data-message обробкою, кастомними каналами, навігацією за payload та lifecycle токена: 1,5–2 дні.







