Developing a Mobile IoT Hub for Device Management
IoT Hub in a mobile app isn't just a device list with on/off buttons. It's a connection manager across multiple protocols, real-time state updates for hundreds of devices simultaneously, and correct handling of situations where half the devices are offline. Poor architecture gives a sluggish UI and unpredictable crashes above 20–30 devices.
Architecture: Unified Device State Bus
Central problem: heterogeneous data sources. Devices come via MQTT, WebSocket, Bluetooth, REST polling. Everything must converge into single Map<DeviceId, DeviceState> that reactively updates UI.
On Android—StateFlow in DeviceRepository at Application level:
class DeviceRepository {
private val _devices = MutableStateFlow<Map<String, DeviceState>>(emptyMap())
val devices: StateFlow<Map<String, DeviceState>> = _devices.asStateFlow()
fun updateDevice(id: String, state: DeviceState) {
_devices.update { current -> current + (id to state) }
}
}
Each transport (MqttManager, WebSocketManager, BleManager) calls repository.updateDevice() on event. ViewModel subscribes via devices.collectAsState() in Compose. No races—update {} is atomic.
Don't create separate StateFlow per device—with 100 devices that's 100 subscriptions in UI, and recomposition in Compose fires sequentially per update, visually appearing as list flicker.
MQTT: Primary IoT Transport
Paho MQTT Client for Android—standard choice. Version 1.2.5+, connect via MqttAndroidClient or coroutine wrapper. QoS 1 for commands (at least once), QoS 0 for telemetry (fire and forget—losing one sensor value isn't critical).
val mqttClient = MqttAsyncClient(brokerUrl, clientId, MemoryPersistence())
val options = MqttConnectOptions().apply {
isAutomaticReconnect = true
isCleanSession = false
connectionTimeout = 10
keepAliveInterval = 30
userName = username
password = password.toCharArray()
}
mqttClient.connect(options).waitForCompletion()
mqttClient.subscribe("devices/+/state", 1) { topic, message ->
val deviceId = topic.split("/")[1]
val state = json.decodeFromString<DeviceState>(message.toString())
repository.updateDevice(deviceId, state)
}
isCleanSession = false restores subscriptions after reconnect. Without this, after network loss you must re-subscribe manually.
Last Will Testament (LWT) detects device disconnection. Device registers LWT message on broker connection; if it drops without explicit disconnect, broker publishes LWT. App subscribes to devices/+/status and updates isOnline = false.
Connection Management: Lifecycle and Background Work
MQTT connection shouldn't die when app minimizes—otherwise push notifications about state changes won't arrive. foreground Service with notification is correct pattern for Android. WorkManager doesn't fit: doesn't guarantee continuous operation.
iOS: Background App Refresh is unreliable for persistent connection. Correct path—APNS: device publishes status via MQTT → backend → sends push via FCM/APNS. App learns state changes via push, not live WebSocket.
UI: Real-Time Device List
Device list with updating data—LazyColumn in Compose or RecyclerView with DiffUtil. Critical: updating one device shouldn't redraw entire list. In Compose—key(device.id) in LazyColumn:
LazyColumn {
items(devices, key = { it.id }) { device ->
DeviceCard(device = device)
}
}
DeviceCard recomposes only on specific device object change. Without key—any device update recomposes the entire list.
Grouping and Filtering
Real installations with 50+ devices need groups (rooms, zones, types) and search. Filter at ViewModel level:
val filteredDevices = combine(devices, searchQuery, selectedGroup) { all, query, group ->
all.values.filter { device ->
(group == null || device.group == group) &&
(query.isEmpty() || device.name.contains(query, ignoreCase = true))
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
combine + stateIn for reactive filtering without manual recalc. SharingStarted.WhileSubscribed(5000) stops stream 5 seconds after last subscriber, saves resources on minimize.
Timeline
Mobile IoT Hub with MQTT, WebSocket, device list, real-time updates: 6–10 weeks. Multi-protocol (BLE, MQTT, REST polling, push notifications) with group constructor: 3–5 months. Cost calculated individually.







