Реализация статусов прочтения сообщений в чате мобильного приложения
Галочки в мессенджере — это не просто 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 флаг и условный запуск трекинга видимости.
Offline-поведение. Статусы, отправленные оффлайн, должны кешироваться локально (Room / CoreData / Hive) и отправляться при восстановлении соединения. Иначе пользователь читает сообщения, но отправитель никогда не узнает об этом.
Что входит в работу
Проектируем схему статусов под ваш тип чата (личный / групповой), реализуем трекинг видимости, батчинг запросов, WebSocket-обновления на стороне отправителя, оффлайн-кеширование. Проверяем корректность на граничных кейсах: быстрый скролл, одновременное открытие на нескольких устройствах, разрыв соединения.
Срок: 3–6 дней в зависимости от сложности чата и наличия готового бэкенда.







