Real-Time Monitoring of IoT Device Status in Mobile Applications
User opens the device list and sees the current status of each — online, offline, power error, low battery. Not "status from 5 minutes ago", but now. Technically, this is a task of bidirectional realtime connection with proper lifecycle management on a mobile device.
Transports for Real-Time Status
MQTT with LWT (Last Will Testament). When a device connects to the broker, it registers an LWT message: if the connection drops without explicit disconnect, the broker publishes {"online": false} to topic devices/{id}/status. Mobile app subscribes to devices/+/status and updates UI on receipt.
This is the best pattern for IoT: offline detection happens automatically, without polling.
WebSocket. For web-based backends. Home Assistant API, custom backend on Laravel with Pusher or Laravel Echo — all via WebSocket. Works when MQTT broker is not directly accessible from the phone.
Server-Sent Events (SSE). Unidirectional HTTP streaming. Simpler than WebSocket, works through any CDN. On Android — OkHttp with EventSource:
val request = Request.Builder().url("$baseUrl/api/devices/stream").build()
val eventSource = EventSources.createFactory(okHttpClient)
.newEventSource(request, object : EventSourceListener() {
override fun onEvent(source: EventSource, id: String?, type: String?, data: String) {
val update = json.decodeFromString<DeviceStatusUpdate>(data)
repository.updateDeviceStatus(update.deviceId, update.status)
}
override fun onFailure(source: EventSource, t: Throwable?, response: Response?) {
// Reconnect via ExponentialBackoff
scheduleReconnect()
}
})
Long polling — outdated pattern, not recommended for mobile. Keeps HTTP connection open, poor with network switching.
MQTT on Android: Lifecycle Management
MQTT connection shouldn't bind to Activity or Fragment lifecycle. Correct — use foreground Service:
class MqttService : Service() {
private lateinit var mqttClient: MqttAsyncClient
override fun onCreate() {
val options = MqttConnectOptions().apply {
isAutomaticReconnect = true
isCleanSession = false
keepAliveInterval = 30
}
mqttClient = MqttAsyncClient(brokerUrl, clientId, MqttDefaultFilePersistence())
mqttClient.connect(options).waitForCompletion()
mqttClient.subscribe("devices/+/status", 1) { topic, message ->
val deviceId = topic.split("/")[1]
// Broadcast or via Repository
DeviceRepository.getInstance().updateStatus(deviceId, message.toString())
}
}
}
When transitioning to background, Android can kill ordinary services. startForeground() with notification protects against kill. On Android 14, foreground service requires explicit type: android:foregroundServiceType="connectedDevice".
Handling Online/Offline Transitions
Three states to reflect in UI:
-
online— device connected, data current -
offline— device not responding (LWT fired or timeout) -
unknown— no connection to broker/backend, status unknown
unknown is separate. If the phone itself lost network, you can't show all devices as offline — that's false. Detect network state via ConnectivityManager.NetworkCallback:
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
viewModel.setConnectionState(ConnectionState.CONNECTED)
}
override fun onLost(network: Network) {
viewModel.setConnectionState(ConnectionState.NO_NETWORK)
// Show "No connection" banner instead of offline statuses
}
}
UI Indicators
Simple green/red dot — readable but uninformative. Better:
- Icon + color: green = online, gray = offline, red = error, yellow = warning
- Last activity time for offline devices: "offline · 2 hours ago"
- For battery devices — battery level next to status
"2 hours ago" time — not database timestamp directly. Format via DateUtils.getRelativeTimeSpanString() on Android or RelativeDateTimeFormatter — otherwise reopening after an hour shows "3 hours ago" only after screen reload.
Implementing real-time status monitoring with MQTT/WebSocket: 2–4 weeks depending on transport and device count.







