On this page
A chat app, a live delivery tracker, a trading dashboard and a news app all show "fresh" data, but each needs a different freshness, and freshness has a price on mobile. Choosing between REST, WebSockets and push is about how fast updates must arrive and what you're willing to spend in battery and complexity.
The communication models
Comparison
| Need | Best fit | Why |
|---|---|---|
| Load data when a screen opens | REST | Simple, cacheable, stateless |
| Instant two-way updates while a screen is open (chat, live collaboration) | WebSocket | Low latency in both directions |
| One-way live updates while open (scores, prices) | WebSocket or Server-Sent Events | Server push without polling |
| Alert users when the app is closed | FCM push | System-managed, battery-friendly delivery |
| Data that changes every few minutes | REST with refresh on screen open or periodic sync | A persistent connection isn't worth the cost |
Mobile realities
- Battery: keeping a radio active drains battery. Keep WebSockets open only while the relevant UI is visible.
- Background limits: Android restricts background network and execution, so you can't rely on a background WebSocket. Use FCM to wake the app.
- Network changes: moving between Wi-Fi and mobile data drops connections. Reconnection with backoff is mandatory.
- Missed messages: after reconnecting, fetch what you missed via REST (for example with
since=lastEventId).
OkHttp WebSocket as a Flow
sealed interface SocketEvent {
data object Connected : SocketEvent
data class Message(val text: String) : SocketEvent
data class Closed(val reason: String) : SocketEvent
}
fun OkHttpClient.webSocketFlow(url: String): Flow<SocketEvent> = callbackFlow {
val socket = newWebSocket(Request.Builder().url(url).build(), object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) { trySend(SocketEvent.Connected) }
override fun onMessage(webSocket: WebSocket, text: String) { trySend(SocketEvent.Message(text)) }
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
trySend(SocketEvent.Closed(reason)); close()
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { close(t) }
})
awaitClose { socket.close(1000, "Collector cancelled") } // closes when the screen stops collecting
}
/** Reconnect with capped exponential backoff while collected. */
fun OkHttpClient.resilientSocket(url: String): Flow<SocketEvent> =
webSocketFlow(url).retryWhen { cause, attempt ->
if (cause is IOException) { delay(minOf(30_000L, 1_000L shl attempt.toInt().coerceAtMost(5))); true }
else false
}Set a ping interval on the OkHttp client (pingInterval(20, TimeUnit.SECONDS)) so dead connections are detected.
Lifecycle-aware usage
class ChatViewModel(client: OkHttpClient, private val repo: ChatRepository) : ViewModel() {
val messages: StateFlow<List<ChatMessage>> = client.resilientSocket("wss://chat.example.com/rooms/42")
.onEach { event ->
when (event) {
SocketEvent.Connected -> repo.syncMissed() // REST catch-up after (re)connect
is SocketEvent.Message -> repo.saveIncoming(event.text) // persist; UI observes Room
is SocketEvent.Closed -> Unit
}
}
.flatMapLatest { repo.observeMessages() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
}With WhileSubscribed, the socket closes a few seconds after the screen stops collecting, which saves battery when the user leaves.
Real-world combinations
Chat app
WebSocket while the conversation is open, FCM for new messages when closed, and REST for history and catch-up.
Delivery tracking
WebSocket or SSE for the live map while visible, and FCM for "driver arriving" when backgrounded.
News feed
REST with pull-to-refresh and periodic WorkManager sync. Real-time sockets would add cost without benefit.
Common mistakes
- Keeping a WebSocket open in a background service "for real-time", which drains battery and fights OS limits.
- No reconnection or catch-up logic, so messages are silently lost.
- Unbounded reconnect loops without backoff during outages.
- Sending auth tokens in query strings, which may end up in logs. Prefer headers or an initial auth message, as your server supports.
Frequently asked questions
Are WebSockets supported by Retrofit?
No. Use OkHttp's WebSocket API directly, or Ktor's WebSocket client.
What about gRPC streaming or MQTT?
Both are valid for specific needs: gRPC for typed bidirectional streams with gRPC backends, and MQTT for IoT-style pub/sub. The same lifecycle and reconnection concerns apply.
Is polling ever okay?
Yes, for infrequent updates while a screen is open, with sensible intervals and stopping when the screen isn't visible.
Conclusion
Use REST for most data, WebSockets for truly real-time, two-way features while the UI is visible, and FCM for anything that must reach users when the app is closed. Wrap sockets in lifecycle-aware Flows with backoff, and always catch up via REST after reconnecting.
Next step: reduce network use further with Caching Strategies for Android Apps.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Networking, persistence and offline sync are where real apps get complicated. Practise building a data layer that handles slow networks, failures and caching, not just the happy path.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.