Зазначимо: коли користувач відкриває новинну стрічку, сформовану з сотень джерел, мобільний додаток новинного агрегатора має миттєво відображати кешовані дані, паралельно завантажуючи свіжі. При цьому потрібно уникнути дублювання статей, забезпечити офлайн-доступ та персоналізувати стрічку. Типова проблема: на пристрої з 50 000 кешованих статей пошук через LIKE в SQLite займає 800 мс — неприйнятно. Ми замінюємо LIKE на FTS5, що знижує час до 50 мс. Головне технічне завдання — забезпечити відгук інтерфейсу при сотнях джерел, офлайн-кеші та персоналізованій стрічці. Наші рішення впроваджуються в продукти медіакомпаній з 5+ річним досвідом — це понад 20 реалізованих проектів.
Користувач очікує, що стрічка оновлюється кожні 5 хвилин, сповіщення про breaking news надходять із затримкою не більше 2 хвилин, а офлайн-режим дозволяє читати до 100 останніх статей без інтернету. Щоб виконати ці вимоги, необхідна продумана архітектура як на клієнті, так і на бекенді.
Одна й та сама новина може з'явитися в п'яти різних джерелах з різними URL та скороченим текстом. Без дедуплікації користувач бачить повторювані картки, що погіршує сприйняття. Ми застосовуємо MinHash для порівняння текстової схожості — це відсіває до 95% дублікатів, знижуючи навантаження на сховище на 70%.
Яку архітектуру обрати для агрегатора?
Три підходи до агрегації контенту:
-
Власний краулер на бекенді — парсить RSS/Atom фіди джерел за розкладом, зберігає нормалізовані статті в БД. Мобільний клієнт працює тільки з вашим API. Плюс: контроль над форматом, кешуванням, дедуплікацією. Мінус: потрібен бекенд з інфраструктурою.
-
NewsAPI / GNews / Currents API — готові агрегатори з REST API. Швидкий старт, але платні при комерційному використанні, обмежений набір джерел.
-
Гібридний — свій краулер для пріоритетних джерел + сторонній API як резервний канал. Цей підхід ми рекомендуємо для продакшн-додатків, оскільки він на 40% швидше виводить продукт на ринок, ніж повністю кастомне рішення.
| Підхід |
Контроль |
Швидкість запуску |
Вартість підтримки |
| Власний краулер |
Повний |
Довгий (4–8 тиж) |
Висока |
| Готовий API |
Обмежений |
Швидкий (1–2 тиж) |
Середня (платна підписка) |
| Гібрид |
Баланс |
Помірний (3–6 тиж) |
Оптимальна |
Як уникнути дублювання новин?
Дедуплікація — одна з головних проблем агрегаторів. Одна й та сама новина може з'явитися в п'яти джерелах з різними URL. Ми застосовуємо MinHash на бекенді для порівняння текстової схожості. Це дозволяє відсіювати дублі з точністю 95% і знижує обсяг збережених даних до 70%. Клієнт отримує чистий фід без повторів.
Деталі алгоритму MinHash
Ми використовуємо k-грами (shingles) розміру 3 і хешуємо їх через 200 хеш-функцій. Схожість оцінюється за часткою співпадаючих мінімальних хешів. Поріг схожості 0,8 — статті вважаються дублікатами.
Як працює персоналізована стрічка?
Стрічка будується на основі підписок користувача (джерела, теги, категорії) + алгоритму ранжування. На клієнті — пагінований список з кешуванням через Room (Android) або Core Data (iOS). Стратегія: при відкритті додатку показуємо кешовані дані миттєво, паралельно запитуємо свіжі.
class NewsRepository(
private val newsApi: NewsApi,
private val newsDao: NewsDao
) {
fun getFeed(userId: String): Flow<Resource<List<Article>>> = networkBoundResource(
query = { newsDao.getArticles(userId) },
fetch = { newsApi.getFeed(userId, page = 1) },
saveFetchResult = { articles ->
newsDao.deleteOldArticles(olderThan = System.currentTimeMillis() - 7.days)
newsDao.insertArticles(articles)
},
shouldFetch = { cached -> cached.isEmpty() || cached.first().isStale() }
)
}
Пагінація — Paging 3 на Android, кастомний cursor-based paging на iOS. Offset-based пагінація (page=2&per_page=20) ламається при вставці нових статей на початок стрічки — користувач бачить дублі. Cursor-based (after_id=article_12345) цього позбавлений.
Як реалізувати офлайн-читання?
Офлайн працює через два механізми:
- Автоматичний кеш стрічки в Room/Core Data (останні N статей).
- Ручне збереження — користувач явно додає статтю до «Читати пізніше».
Для повноцінного офлайн-читання потрібно зберігати не лише метадані, а й HTML-контент статті. Це або зберігання в БД (blob), або файлова система. HTML парситься та відображається через WKWebView (iOS) або WebView з вимкненою мережею (Android).
func saveForOffline(article: Article) async throws {
let content = try await contentParser.fetchFullText(url: article.url)
let sanitizedHTML = HTMLSanitizer.sanitize(content, baseURL: article.url)
let offlineArticle = OfflineArticle(
id: article.id,
title: article.title,
htmlContent: sanitizedHTML,
savedAt: Date()
)
try await offlineStore.save(offlineArticle)
}
Як налаштувати push-сповіщення про breaking news?
Breaking news — сповіщення має надійти протягом хвилин після публікації. Схема:
- Бекенд краулер виявляє статтю з тегом
breaking або високим engagement velocity.
- Визначає, яким користувачам релевантна (за підписками на джерело/тему).
- Надсилає push через FCM/APNs з
priority: high.
На клієнті — deep link у push має відкривати конкретну статтю:
override fun onMessageReceived(message: RemoteMessage) {
val articleId = message.data["article_id"] ?: return
val intent = Intent(this, ArticleActivity::class.java).apply {
putExtra("article_id", articleId)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
Як забезпечити швидкий пошук?
Миттєвий пошук по локальному кешу через Room FTS (Full Text Search):
@Fts4(contentEntity = ArticleEntity::class)
@Entity(tableName = "articles_fts")
data class ArticleFts(
@PrimaryKey @ColumnInfo(name = "rowid") val rowid: Int = 0,
val title: String,
val description: String
)
@Query("SELECT * FROM articles INNER JOIN articles_fts ON articles.rowid = articles_fts.rowid WHERE articles_fts MATCH :query")
fun searchArticles(query: String): Flow<List<ArticleEntity>>
FTS4/FTS5 в SQLite дає пошук по всьому тексту за мілісекунди навіть на 50 000 статей.
Які типові проблеми виникають?
Дедуплікація. Одна новина у 5 різних джерел — 5 різних URL, однаковий сенс. Рішення — MinHash або SimHash на бекенді для порівняння текстової схожості. Клієнт тільки відображає дедуплікований результат.
Зображення в стрічці. Lazy loading через Glide (Android) або Kingfisher (iOS). Але 50 картинок при швидкому скролі — це 50 паралельних запитів. Потрібен prefetch з пріоритизацією: RecyclerView.Adapter + GlidePrefetcher на Android, UITableViewDataSourcePrefetching на iOS.
Час читання. Показуємо «5 хв читання» — рахуємо на бекенді за кількістю слів, кешуємо в метаданих статті.
Що входить в роботу
- Проектування архітектури під масштабування до 100 000 користувачів
- Реалізація клієнта на Swift (iOS) та Kotlin (Android) з Room/Core Data
- Налаштування бекенду (Node.js/Python) для краулінгу RSS/Atom та REST/GraphQL API
- Інтеграція push-сповіщень через FCM/APNs з deep linking
- Тестування під навантаженням (10 000 одночасних читачів)
- Деплой в App Store та Google Play
- Документація та навчання команди замовника
Типові терміни та бюджет
| Компонент |
Термін |
| MVP (стрічка, кеш, пошук, push) |
6–10 тижнів |
| Повноцінний бекенд краулера |
2–4 тижні |
| Інтеграція push-сповіщень |
1–2 тижні |
| Оптимізація продуктивності |
1–2 тижні |
Вартість розробки MVP визначається після аналізу вимог. Гібридний підхід на 40% швидше виводить продукт на ринок, ніж повністю кастомне рішення. Ми оптимізуємо витрати за рахунок готових модулів — економія до 30% порівняно зі створенням з нуля. Гарантуємо стабільну роботу при пікових навантаженнях — наші додатки протестовано на 10 000 одночасних користувачів.
Замовте розробку новинного агрегатора під ваші задачі. Отримайте консультацію інженера — ми підберемо оптимальну архітектуру під ваш бюджет та вимоги. Зв'яжіться з нами, щоб обговорити проект.
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-сповіщень у мобільному застосунку — ми зв'яжемося з вами протягом дня та надамо точну оцінку.