Реалізація індикатора набору тексту у мобільному чаті
«Три точки» у месенджері — один із тих дрібних елементів UX, які користувачі помічають тільки коли його немає. Реалізація здається тривіальною, поки не упреш у дебаунс, консистентність між пристроями та правильну роботу при нестабільному з'єднанні.
Як влаштована механіка на практиці
Базова схема: клієнт надсилає подію typing_start при кожному натиску клавіші, собеседник бачить індикатор, через N секунд без нових подій індикатор гасне. На папері просто. На практиці — три типові проблеми.
Флуд подій. Без дебаунсу кожен onTextChanged (Android) або textDidChange (iOS) генерує мережевий виклик. Користувач, пишучи повідомлення за 5 секунд, створює 20–30 зайвих запитів. Правильне рішення — надсилати typing_start тільки якщо пройшло більше 2–3 секунд з моменту попередної відправки, та typing_stop з затримкою ~4 секунди після останнього символу.
На Android це реалізується через Handler.postDelayed або оператор debounce у RxJava/Kotlin Flow:
private var typingJob: Job? = null
fun onTextChanged(text: String) {
if (text.isEmpty()) {
sendTypingStop()
return
}
typingJob?.cancel()
typingJob = viewModelScope.launch {
delay(TYPING_THROTTLE_MS) // 3000ms
sendTypingStart()
}
}
На iOS аналогічна логіка через Timer.scheduledTimer або Combine:
private var typingCancellable: AnyCancellable?
func textDidChange(_ text: String) {
typingCancellable?.cancel()
typingCancellable = Just(text)
.delay(for: .seconds(3), scheduler: RunLoop.main)
.sink { [weak self] _ in self?.sendTypingStart() }
}
Транспортний шар. Індикатор набору — це ефемерний стан, який не потребує гарантій доставки. Тому REST/HTTP тут надлишковий. Оптимальний вибір — WebSocket або Firebase Realtime Database. Через WebSocket надсилаємо легкий JSON-пакет {"type":"typing","chat_id":"...","user_id":"..."}. Через Firebase — запис у ефемерний вузол /typing/{chatId}/{userId} з TTL через onDisconnect().removeValue(). Другий варіант автоматично чистить стан при розриві з'єднання — критично.
Відображення на приймачі. Після отримання події потрібно показати індикатор та запустити таймер очистки (~5 секунд). Якщо за цей час прийде нова подія — таймер скидається. Анімація трьох точок: на iOS — кастомний CABasicAnimation або Lottie, на Android — AnimationDrawable або Lottie-анімація через LottieAnimationView. Flutter розв'язує це через AnimatedOpacity + Timer.
Що робимо
Аналізуємо існуючий транспортний шар чата (WebSocket, Firebase, XMPP — у всіх свої нюанси), додаємо дебаунс на клієнті, реалізуємо відображення з правильною анімацією та автоочисткою стану. Якщо чата ще немає — проектуємо з нуля під потрібний стек.
Строка: 1–3 дня в залежності від складності поточної архітектури чата.







