Implementation of Typing Indicator in Mobile Chat
Three dots in a messenger — one of those small UX elements users notice only when it's missing. Implementation seems trivial until you hit debounce, consistency across devices, and proper behavior on unstable connections.
How the Mechanics Works in Practice
Basic scheme: client sends typing_start event on each key press, peer sees indicator, after N seconds without new events indicator fades. On paper — simple. In practice — three typical problems.
Event flood. Without debounce, each onTextChanged (Android) or textDidChange (iOS) generates network call. User typing a message in 5 seconds creates 20–30 unnecessary requests. Correct solution — send typing_start only if more than 2–3 seconds passed since previous send, and typing_stop with delay ~4 seconds after last character.
On Android this is implemented via Handler.postDelayed or debounce operator in 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()
}
}
On iOS similar logic via Timer.scheduledTimer or 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() }
}
Transport layer. Typing indicator is ephemeral state that doesn't require delivery guarantees. Therefore REST/HTTP is overkill here. Optimal choice — WebSocket or Firebase Realtime Database. Via WebSocket send lightweight JSON packet {"type":"typing","chat_id":"...","user_id":"..."}. Via Firebase — entry in ephemeral node /typing/{chatId}/{userId} with TTL via onDisconnect().removeValue(). Second variant automatically cleans state on disconnection — critical.
Display on receiver. After receiving event need to show indicator and start cleanup timer (~5 seconds). If new event arrives within — reset timer. Animation of three dots: on iOS — custom CABasicAnimation or Lottie, on Android — AnimationDrawable or Lottie animation via LottieAnimationView. Flutter solves this via AnimatedOpacity + Timer.
What We Do
Analyze existing chat transport layer (WebSocket, Firebase, XMPP — all have their nuances), add debounce on client, implement display with proper animation and state auto-cleanup. If no chat exists yet — design from scratch for required stack.
Timeline: 1–3 days depending on current chat architecture complexity.







