๐Ÿ”Œ APIs, Databases, and Backend Integration

WebSockets vs REST APIs in Android

REST is request and response: the app asks, the server answers. WebSockets keep a connection open so the server can push data instantly. Real-time sounds great until you consider battery, lifecycle and flaky mobile networks. Here's how to choose, and how to implement WebSockets robustly.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Intermediate
On this page
  1. The communication models
  2. Comparison
  3. Mobile realities
  4. OkHttp WebSocket as a Flow
  5. Lifecycle-aware usage
  6. Real-world combinations
  7. Common mistakes
  8. Frequently asked questions
  9. Conclusion

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

REST polling compared with a WebSocket connection Top: with REST, the client sends a request and gets a response; to get updates it polls repeatedly, many requests return nothing new. Bottom: with a WebSocket, after one handshake the connection stays open and either side can send messages at any time. REST (request โ†’ response, polling for updates) App Server GET /messages?since=โ€ฆ (nothing new) GET again after 10 s WebSocket (one connection, both directions) App Server persistent connection: messages pushed instantly, either way
Polling wastes requests when nothing changes. A WebSocket is efficient while open, but keeping it open has costs on mobile.

Comparison

NeedBest fitWhy
Load data when a screen opensRESTSimple, cacheable, stateless
Instant two-way updates while a screen is open (chat, live collaboration)WebSocketLow latency in both directions
One-way live updates while open (scores, prices)WebSocket or Server-Sent EventsServer push without polling
Alert users when the app is closedFCM pushSystem-managed, battery-friendly delivery
Data that changes every few minutesREST with refresh on screen open or periodic syncA 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

LiveUpdates.ktKotlin
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

ChatViewModel.ktKotlin
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.

See all our apps โ†’

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.

Share: LinkedIn X / Twitter WhatsApp