DEV Community

vmodal_ai
vmodal_ai

Posted on

Real-Time Android Apps with Kotlin, WebSockets & Flow

Real-Time Android Apps with Kotlin, WebSockets & Flow

Real-time applications need data to move from the server to the Android device without requiring constant polling.

Examples include:

  • chat
  • live dashboards
  • delivery tracking
  • multiplayer features
  • IoT monitoring
  • notifications

A typical architecture is:

Server
   |
WebSocket
   |
Android WebSocket Client
   |
Kotlin Flow
   |
ViewModel
   |
Jetpack Compose / Views
Enter fullscreen mode Exit fullscreen mode

Why WebSockets?

Polling:

Client -> Request
Server -> Response

wait

Client -> Request
Server -> Response
Enter fullscreen mode Exit fullscreen mode

WebSockets maintain a persistent connection:

Client <================> Server
        bidirectional
Enter fullscreen mode Exit fullscreen mode

This is suitable for continuous real-time updates.

WebSocket Libraries

On Android, you can use a WebSocket-capable networking library such as OkHttp.

Example dependency:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:<version>")
}
Enter fullscreen mode Exit fullscreen mode

Use the current compatible version for your project.

Create a WebSocket Client

class RealtimeClient(
    private val client: OkHttpClient,
    private val url: String
) {
    fun connect(listener: WebSocketListener): WebSocket {
        val request = Request.Builder()
            .url(url)
            .build()

        return client.newWebSocket(request, listener)
    }
}
Enter fullscreen mode Exit fullscreen mode

Receive Messages

class RealtimeListener : WebSocketListener() {

    override fun onOpen(
        webSocket: WebSocket,
        response: Response
    ) {
        println("WebSocket connected")
    }

    override fun onMessage(
        webSocket: WebSocket,
        text: String
    ) {
        println("Message: $text")
    }

    override fun onFailure(
        webSocket: WebSocket,
        t: Throwable,
        response: Response?
    ) {
        println("WebSocket error: ${t.message}")
    }
}
Enter fullscreen mode Exit fullscreen mode

In production, do not put application business logic directly in the listener.

Convert Messages to Flow

Kotlin Flow provides a clean way to expose asynchronous events.

class RealtimeRepository {

    private val _messages = MutableSharedFlow<String>(
        extraBufferCapacity = 64
    )

    val messages: SharedFlow<String> = _messages.asSharedFlow()

    fun onMessage(text: String) {
        _messages.tryEmit(text)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the rest of the application can collect messages using Flow.

ViewModel

class ChatViewModel(
    private val repository: RealtimeRepository
) : ViewModel() {

    val messages = repository.messages
        .map { text -> ChatMessage(text) }
        .stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(5000),
            emptyList()
        )
}
Enter fullscreen mode Exit fullscreen mode

The exact state transformation will depend on your application's requirements.

Lifecycle-Aware Collection

With Jetpack Compose, collect state using lifecycle-aware APIs where appropriate.

Conceptually:

@Composable
fun ChatScreen(viewModel: ChatViewModel) {
    val messages by viewModel.messages.collectAsStateWithLifecycle()

    // Render messages.
}
Enter fullscreen mode Exit fullscreen mode

This helps avoid unnecessary collection when the UI is not active.

Sending Messages

fun send(webSocket: WebSocket, message: String) {
    webSocket.send(message)
}
Enter fullscreen mode Exit fullscreen mode

For structured messages, serialize a data class:

@Serializable
data class ChatRequest(
    val type: String,
    val message: String
)
Enter fullscreen mode Exit fullscreen mode

Then encode it as JSON before sending.

Reconnection

Mobile connections frequently disappear.

Use a strategy such as:

Connected
   |
Connection lost
   |
Wait
   |
Reconnect
   |
Connected
Enter fullscreen mode Exit fullscreen mode

Exponential backoff is preferable to reconnecting in a tight loop.

Connection State

Expose connection state explicitly:

sealed interface ConnectionState {
    data object Disconnected : ConnectionState
    data object Connecting : ConnectionState
    data object Connected : ConnectionState
    data class Error(val message: String) : ConnectionState
}
Enter fullscreen mode Exit fullscreen mode

This allows the UI to show meaningful status.

Heartbeats

Some servers or network infrastructure close idle connections.

A heartbeat mechanism can help detect stale connections.

For example:

Client -> ping
Server -> pong
Enter fullscreen mode Exit fullscreen mode

Use the protocol or server framework's recommended heartbeat behavior.

Threading

WebSocket callbacks should not perform expensive work directly.

A clean pipeline is:

WebSocket callback
      ↓
Repository
      ↓
Flow
      ↓
ViewModel
      ↓
UI
Enter fullscreen mode Exit fullscreen mode

Keep CPU-heavy processing off the main thread.

Security

Use secure WebSockets in production:

wss://example.com/socket
Enter fullscreen mode Exit fullscreen mode

Authenticate connections and validate all messages received from the server.

Conclusion

WebSockets provide the transport layer, while Kotlin Flow provides a powerful way to expose real-time events to the rest of an Android application.

Keeping the WebSocket client, repository, ViewModel, and UI responsibilities separate makes real-time Android applications easier to test and maintain.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)