Реалізація статусів прочитання повідомлень у мобільному чаті
Галочки у месенджері — це не просто UI. За ними стоїть синхронізація стану між пристроями, розв'язання конфліктів при нестабільному з'єднанні та правильна робота з пагінацією історії. Без нормальної архітектури отримуємо класику: повідомлення показує «прочитано», хоча користувач його не бачив, або статус не оновлюється після перезапуску застосунку.
Архітектура статусів
Типова модель: у кожного повідомлення є status — sent, delivered, read. Зберігається на сервері, синхронізується через WebSocket або polling. Найчастіша помилка — помічати повідомлення як прочитане при його отриманні (onMessage), а не при його реальній появі на екрані.
Правильний підхід — відстежувати видимість через:
-
Android:
RecyclerView.OnScrollListener+LinearLayoutManager.findFirstCompletelyVisibleItemPosition()/findLastCompletelyVisibleItemPosition(). Помічаємо прочитаними тільки повідомлення у видимій області. -
iOS:
UITableView.indexPathsForVisibleRows+ делегатний методtableView(_:willDisplay:forRowAt:). -
Flutter:
VisibilityDetectorз пакетуvisibility_detectorабо кастомнийScrollNotificationlistener.
Батчинг запитів критичен: не надсилаємо read для кожного повідомлення окремо. Збираємо масив ID та надсилаємо одним запитом з дебаунсом 500–1000 мс після зупинки скролу.
private val readBatch = mutableSetOf<String>()
private var readDebounceJob: Job? = null
fun markVisible(messageIds: List<String>) {
readBatch.addAll(messageIds)
readDebounceJob?.cancel()
readDebounceJob = viewModelScope.launch {
delay(700)
if (readBatch.isNotEmpty()) {
sendReadReceipts(readBatch.toList())
readBatch.clear()
}
}
}
Відображення та синхронізація
Індикатори статусу на стороні відправника оновлюються через WebSocket-подію або Firebase listener. Важливий момент: у групових чатах «прочитано» означає «прочитано всіма» або «прочитано хоча б одним» — це бізнес-рішення, яке впливає на схему даних. У Telegram-моделі зберігається read_by_count, у WhatsApp-моделі — read_by: [userId].
Загрузка історії через пагінацію створює окрему проблему: якщо користувач листає старі повідомлення, вони не повинні автоматично помічатися як прочитані — тільки поточні. Розв'язується через isAtBottom флаг та умовний запуск трекінга видимості.
Офлайн-поведінка. Статуси, відправлені офлайн, повинні кешуватися локально (Room / CoreData / Hive) та надсилатися при відновленні з'єднання. Інакше користувач читає повідомлення, але відправник ніколи не дізнається про це.
Що входить в роботу
Проектуємо схему статусів під ваш тип чату (особистий / груповий), реалізуємо трекінг видимості, батчинг запитів, WebSocket-оновлення на стороні відправника, офлайн-кешування. Перевіряємо коректність на граничнихケйсах: швидкий скролл, одночасне відкриття на кількох пристроях, розрив з'єднання.
Строка: 3–6 днів в залежності від складності чату та наявності готового бекенду.







