Локальні сповіщення в мобільному додатку: iOS та Android
Клієнт поскаржився, що нагадування в трекері звичок перестали працювати після оновлення iOS. Причина — перевищено ліміт у 64 сповіщення, і система мовчки їх видалила. Ми переписали планувальник із пріоритетною чергою: найближчі 64 тримаємо в системі, решту переплановуємо при кожному відкритті. Цей спосіб ми застосовували в більш ніж 30 проєктах. Локальні сповіщення — єдиний тип, що не потребує сервера. Додаток сам планує їх через системний API: за часом, за тригером календаря або при вході в геозону.
Ми — команда мобільних розробників з 5-річним досвідом і більш ніж 50 реалізованих проєктів. Сертифіковані спеціалісти з iOS та Android. Реалізуємо локальні сповіщення під ключ: від схеми тригерів до публікації в сторах. Оцінимо ваш проєкт за 1 день — просто напишіть нам. Замовте впровадження й отримайте гарантію на код 3 місяці.
Як обійти ліміт у 64 сповіщення на iOS?
UNUserNotificationCenter дозволяє запланувати не більше 64 сповіщень одночасно. Для трекерів звичок, будильників або календарів цього недостатньо. Рішення — динамічна черга. Зберігайте всі заплановані нагадування в локальній БД (Core Data або Realm). При запуску додатка вибирайте найближчі 64 і реєструйте їх. При спрацьовуванні або скасуванні — оновлюйте чергу. Користувач ніколи не дізнається про ліміт. Ось приклад планування за часом:
import UserNotifications
// 1. За часом (через N секунд)
let content = UNMutableNotificationContent()
content.title = "Нагадування про зустріч"
content.body = "Зустріч з командою через 15 хвилин"
content.sound = .default
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 900, repeats: false)
let request = UNNotificationRequest(identifier: "meeting-reminder-123",
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request)
// 2. За датою/часом (повторюване щодня о 9:00)
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0
let dailyTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// 3. По геозоні
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 50.45, longitude: 30.52),
radius: 200,
identifier: "office-zone")
region.notifyOnEntry = true
region.notifyOnExit = false
let geoTrigger = UNLocationNotificationTrigger(region: region, repeats: false)
Чому після перезавантаження Android сповіщення зникають?
AlarmManager скидається при вимкненні пристрою. Якщо не обробити BOOT_COMPLETED, всі нагадування зникнуть. Ми завжди додаємо BroadcastReceiver на BOOT_COMPLETED, який читає активні нагадування з Room і переплановує їх через AlarmManager. Для періодичних завдань без точного часу використовуємо WorkManager — він автоматично відновлюється після reboot. Ось приклад точного аларму:
val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("title", "Нагадування про зустріч")
putExtra("body", "Зустріч через 15 хвилин")
putExtra("notification_id", 123)
}
val pendingIntent = PendingIntent.getBroadcast(context, 123, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerAtMillis,
pendingIntent
)
NotificationReceiver — BroadcastReceiver, який будує і показує сповіщення:
class NotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val notification = NotificationCompat.Builder(context, "reminders_channel")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(intent.getStringExtra("title"))
.setContentText(intent.getStringExtra("body"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context)
.notify(intent.getIntExtra("notification_id", 0), notification)
}
}
На Android 12+ точні аларми потребують дозволу SCHEDULE_EXACT_ALARM. Для повторюваних — WorkManager з PeriodicWorkRequest простіше та надійніше.
Як виглядає покрокова реалізація?
-
Проєктування схеми сповіщень. Визначаємо типи тригерів: час, календар, геозона. Для кожного — контент, звук, значок, категорія. Враховуємо сценарії: одноразові, повторювані, зі скасуванням.
-
Налаштування каналів. На Android створюємо канали (
NotificationChannel), на iOS — категорії (UNNotificationCategory). Це дозволяє користувачеві керувати важливістю та групуванням.
- Розробка планувальника. На iOS —
UNUserNotificationCenter з пріоритетною чергою. На Android — AlarmManager для точних + WorkManager для періодичних. Додаємо обробку BOOT_COMPLETED.
- Тестування. Перевіряємо на реальних пристроях: сон, перезавантаження, регіон, ліміти. Використовуємо TestFlight та Firebase App Distribution.
- Оптимізація під стори. Враховуємо вимоги до дозволів (точні аларми, фонова геолокація) і оформляємо код для рев'ю.
Порівняння iOS та Android
| Параметр |
iOS |
Android |
| API |
UNUserNotificationCenter |
AlarmManager + NotificationManager |
| Макс. запланованих |
64 |
без ліміту (але залежить від пристрою) |
| Геозони |
вбудований UNLocationNotificationTrigger |
GeofencingClient (Google Play Services) |
| Повторення |
UNCalendarNotificationTrigger |
WorkManager або власний через AlarmManager |
| Обробка reboot |
не потрібна (iOS сама відновлює) |
обов'язковий BOOT_COMPLETED BroadCastReceiver |
Типові сценарії та їх реалізація
| Сценарій |
iOS |
Android |
| Нагадування через 15 хвилин |
UNTimeIntervalNotificationTrigger |
AlarmManager.setExact |
| Щоденне о 9:00 |
UNCalendarNotificationTrigger |
WorkManager з PeriodicWorkRequest |
| При вході в геозону (офіс) |
UNLocationNotificationTrigger |
GeofencingClient з переходом ENTER |
| Нагадування з пріоритетом (термінове) |
content.interruptionLevel = .timeSensitive |
NotificationCompat.PRIORITY_HIGH з каналом високого пріоритету |
Як обробити натискання на сповіщення?
На iOS потрібно реалізувати UNUserNotificationCenterDelegate і метод userNotificationCenter(_:didReceive:withCompletionHandler:). На Android — вказати PendingIntent з action, який відкриває потрібний екран за deep link (App Links або scheme). Проконсультуйтеся з нами щодо інтеграції — ми допоможемо налаштувати правильну навігацію.
Що входить у нашу роботу
- Проєктна документація: схеми тригерів, діаграми станів.
- Вихідний код з коментарями на Swift та Kotlin.
- Налаштування сертифікатів і ключів (APNs, Google Play Store).
- Тестовий білд через TestFlight/Firebase App Distribution.
- Допомога з публікацією та модерацією.
- Гарантія на код 3 місяці.
Строки
Реалізація базового функціоналу (час + календар) на одній платформі — 3 робочі дні. З геозонами та двоплатформенна версія — до 6 днів. Строки можуть змінюватися залежно від складності, але ми завжди даємо точну оцінку після аналізу вашого проєкту. Зв'яжіться з нами, щоб обговорити ваш проєкт і отримати безкоштовну оцінку. Замовте реалізацію локальних сповіщень прямо зараз — ваші користувачі точно не пропустять важливе.
Приклад обробки геозон на Android
val geofence = Geofence.Builder()
.setRequestId("office-zone")
.setCircularRegion(50.45, 30.52, 200f)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
geofencingClient.addGeofences(request, geofencePendingIntent)
Геозонні сповіщення потребують дозволу ACCESS_FINE_LOCATION і на Android 10+ — ACCESS_BACKGROUND_LOCATION. Останній — окремий запит, користувач повинен явно обрати «Дозволити завжди» в налаштуваннях.
https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions
Push-сповіщення в мобільному застосунку: APNs, FCM, сегментація, rich push
Ми впровадили push-сповіщення в мобільному застосунку для 50+ проєктів — від стартапів до enterprise з аудиторією 10M+ користувачів. Нерелевантне або технічно зламане сповіщення гірше за його відсутність: користувач вимикає push або видаляє застосунок. Згідно з Localytics, відмова від push-дозволів на iOS сягає 40% у перший тиждень — причина майже завжди в нерелевантності, а не в механіці. Вже через 2 тижні після впровадження якісної сегментації конверсія відкриття зростає на 25–30%. Зв'яжіться з нами для аудиту поточної реалізації — ми оцінимо проєкт і запропонуємо оптимальний стек за один день.
Як працює інфраструктура: APNs та FCM
APNs — єдиний канал доставки на iOS. Все інше (OneSignal, Braze, Airship) — обгортки поверх нього. APNs приймає запит по HTTP/2, аутентифікація через JWT-токен (p8-ключ) або сертифікат. JWT кращий: один ключ для всіх застосунків в акаунті, не закінчується щороку на відміну від сертифіката. (Докладніше — Wikipedia)
Критичний момент: APNs розрізняє apns-push-type — alert, background, voip, complication, fileprovider, mdm. Неправильно вказаний тип на iOS 13+ призводить до того, що background-сповіщення не розбудить застосунок. Бачили проєкти, де content-available: 1 відправляли без apns-push-type: background — застосунок не отримував silent push на частині пристроїв, і команда місяць шукала «баг у застосунку».
FCM на Android працює через Google Play Services. Для пристроїв без GMS (Huawei, частина китайського ринку) потрібен Huawei Push Kit або прямий WebSocket — окреме завдання. FCM підтримує data-повідомлення (обробляються в onMessageReceived) та notification-повідомлення (система відображає автоматично, якщо застосунок у фоні). Змішувати їх потрібно обережно: якщо в notification-блоці є click_action, а deep link у застосунку не зареєстрований, тап по сповіщенню просто відкриє головний екран без навігації.
| Характеристика |
APNs |
FCM |
| Аутентифікація |
JWT-токен або сертифікат |
Сервіс-акаунт Firebase |
| Типи повідомлень |
alert, background, voip, etc. |
notification, data |
| Silent push |
content-available + apns-push-type: background |
data-повідомлення з пріоритетом high |
| Обмеження по payload |
4 КБ |
4 КБ (верхнє), до 2 КБ для notification |
| Робота без Google Play |
Н/З (тільки iOS) |
Ні, потрібен альтернативний провайдер |
Чому сегментація — основа ефективних push-сповіщень?
Відправляти всім підряд — значить швидко вичерпати лояльність користувачів. Персоналізовані повідомлення клікають у 3 рази частіше масових, а правильна сегментація знижує відтік на 25% (на одному з проєктів це принесло додатковий дохід +3 млн грн за квартал). Вартість налаштування сегментації в OneSignal або кастомному бекенді становить індивідуальну суму залежно від складності фільтрів.
Нормальна сегментація будується на кількох рівнях.
| Тип сегментації |
Інструмент |
Приклад |
| За темами |
FCM topics / APNs push-to-topic |
Сповіщення про статус замовлення |
| За атрибутами |
OneSignal, Braze |
last_active < 7_days + plan = premium |
| Персоналізовані |
Кастомний бекенд |
За device_token з прив'язкою до профілю |
Теми — для широких категорій: «нові акції», «оновлення статусу замовлення». Користувач підписується через FirebaseMessaging.getInstance().subscribeToTopic("orders"). Просто, але нема гнучкої фільтрації.
Сегменти за атрибутами — через OneSignal, Braze або кастомний бекенд. Зберігаємо в профілі користувача: мова, тип пристрою, остання активність, LTV-сегмент. Сповіщення йде тільки тим, у кого last_active < 7_days та plan = premium. OneSignal дозволяє будувати такі фільтри в інтерфейсі без коду.
Персоналізовані — за конкретним device_token. Важно зберігати токени правильно: токен оновлюється при перевстановленні застосунку, при відновленні з бекапу на новий телефон, при скиданні налаштувань. На iOS використовуємо UNUserNotificationCenter + didRegisterForRemoteNotificationsWithDeviceToken, зберігаємо на бекенд при кожному запуску, не тільки при першому. Інакше через 3 місяці 30% токенів у базі застарілі.
Що таке rich push і як він підвищує конверсію?
Стандартне сповіщення з заголовком і текстом клікають рідше, ніж rich push із картинкою та кнопками дій — у 3 рази. Але реалізація rich push — окрема робота на кожній платформі.
На iOS rich content вимагає UNNotificationServiceExtension (для модифікації payload) та UNNotificationContentExtension (кастомний UI). Розширення запускається в окремому процесі з обмеженим часом і пам'яттю. Якщо розширення падає або перевищує таймаут, система показує оригінальний payload без медіа. Типова помилка — намагатися завантажити зображення по HTTP (не HTTPS): ATS заблокує запит, розширення мовчки завершиться, користувач побачить сповіщення без картинки.
На Android з API 26+ сповіщення прив'язані до NotificationChannel. Якщо канал створений з IMPORTANCE_LOW, звук і вібрація недоступні. Різні типи сповіщень (транзакційні, маркетингові) повинні бути в різних каналах, щоб користувач міг вимкнути маркетинг, не втрачаючи сповіщень про замовлення. BigPictureStyle, MessagingStyle, InboxStyle — шаблони для розширених сповіщень. MessagingStyle з Person та аватарками — найкращий вибір для чатів.
| Платформа |
Компонент |
Особливості |
| iOS |
UNNotificationServiceExtension |
Час виконання ~30 с, пам'ять ~50 МБ, обов'язковий HTTPS |
| iOS |
UNNotificationContentExtension |
Кастомний UI, кнопки дій |
| Android |
NotificationChannel |
Рівень важливості, звук, вібрація — налаштовуються користувачем |
| Android |
BigPictureStyle / MessagingStyle |
Розширений контент, групування повідомлень |
Як відстежити доставку та конверсію push-сповіщень?
Відправити сповіщення — половина справи. Важно знати: доставлено воно, відкрито, чи привело до цільової дії.
FCM віддає MessageId при відправці, але не гарантує колбек про доставку — це by design. Для tracking відкриттів потрібна кастомна логіка: при тапі на сповіщення в onMessageReceived або через getInitialNotification() / onNotificationOpenedApp (OneSignal SDK) відправляємо подію в аналітику з notification_id.
OneSignal надає вбудовану аналітику доставки та CTR. Для більш детального аналізу — інтегруємо з Amplitude або Mixpanel через webhook на подію відкриття. Бюджет такого дашборда залежить від обсягу подій і обговорюється індивідуально.
Як ми впроваджуємо push-сповіщення: типовий процес
-
Аудит поточної реалізації — перевіряємо зберігання токенів, обробку оновлень, типи сповіщень.
-
Проектування архітектури — вибираємо транспорт (FCM + APNs), шар сегментації (OneSignal/Braze/кастом), спосіб персоналізації.
-
Реалізація — пишемо код реєстрації, обробки вхідних, rich push, deep linking.
-
Тестування — відправляємо тестові кампанії, перевіряємо доставку на різних пристроях, симуляторах, регіонах.
-
Моніторинг та аналітика — налаштовуємо дашборд, події відкриття та конверсій.
-
Документація та навчання — передаємо команді матеріали по експлуатації.
Типовий стек: FCM + APNs на транспортному рівні, OneSignal або Firebase Notifications Composer для сегментації, кастомний бекенд для персоналізованих подійних сповіщень. Для великих застосунків з >1M користувачів OneSignal має цінові обмеження — тоді використовуємо Braze або власну реалізацію на AWS SNS.
Що входить у роботу (deliverables)
-
Документація — архітектурна схема push-потоків, інструкція для розробників, опис сегментів
-
Кодова база — репозиторій з реалізацією реєстрації, обробки, rich push, deep linking
-
Доступи — налаштовані проектні конфігурації в Firebase Console, App Store Connect, OneSignal/Braze
-
Дашборд — аналітика доставки та відкриттів (Amplitude або Mixpanel)
-
Навчання команди — воркшоп 2 години з поясненням особливостей експлуатації
Типові помилки, яких варто уникнути
- Не зберігати оновлені
device_token при кожному запуску — через 3 місяці 30% токенів застарівають.
- Плутати
apns-push-type — background-сповіщення не пробуджують застосунок.
- Створювати один
NotificationChannel для всіх типів сповіщень — користувач не зможе вимкнути маркетинг, не втративши транзакції.
- Завантажувати медіа в rich push по HTTP — ATS блокує запит на iOS.
- Не перевіряти deep link у таргетингу — переходи йдуть на головний екран.
Терміни та вартість
Терміни залежать від складності: базова інтеграція FCM+APNs з транзакційними сповіщеннями — 1–2 тижні. Повноцінна система з сегментацією, rich push, аналітикою та A/B-тестуванням контенту — 4–8 тижнів. Вартість розраховується індивідуально після аудиту.
Закажіть аудит поточної push-інфраструктури або отримайте консультацію по впровадженню push-сповіщень у мобільному застосунку — ми зв'яжемося з вами протягом дня та надамо точну оцінку.