Реализация дашборда IoT-телеметрии в мобильном приложении
IoT-дашборд — экран, который показывает данные с нескольких сенсоров одновременно: текущие значения, тренды, исторические графики, статусы устройств. Ключевая проблема — производительность при большом числе виджетов и часто обновляющихся данных. Неправильная архитектура даёт задержку UI, пропуск обновлений и расход батареи.
Архитектура данных для дашборда
Дашборд агрегирует данные из разных источников: MQTT-топики с realtime телеметрией, REST API для исторических данных, WebSocket для событий. Всё это нужно объединить в единую ViewModel, которая реактивно обновляет виджеты.
На Android — combine нескольких StateFlow:
class DashboardViewModel : ViewModel() {
private val temperatureFlow = mqttRepository.getTopicFlow("sensors/+/temperature")
private val humidityFlow = mqttRepository.getTopicFlow("sensors/+/humidity")
private val devicesFlow = deviceRepository.devices
val dashboardState = combine(
temperatureFlow,
humidityFlow,
devicesFlow
) { temperatures, humidities, devices ->
DashboardState(
sensors = devices.map { device ->
SensorWidgetData(
id = device.id,
name = device.name,
temperature = temperatures[device.id],
humidity = humidities[device.id],
isOnline = device.isOnline
)
}
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), DashboardState())
}
SharingStarted.WhileSubscribed(5_000) — поток останавливается через 5 секунд после того, как экран уходит в background. Экономит соединение, данные подгрузятся при возврате.
Виджеты: что и как рендерить
Типичный IoT-дашборд содержит:
- Числовые виджеты — текущее значение + единица измерения + статус (норма/предупреждение/критично)
- Линейные мини-графики — тренд за последний час
- Gauge/Speedometer — для параметров с явными границами (давление, уровень CO2)
- Карточки статуса устройств — онлайн/офлайн + последние данные
На Android Compose: каждый виджет — отдельный @Composable с ключом устройства. LazyVerticalGrid для сетки виджетов:
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(dashboardState.sensors, key = { it.id }) { sensor ->
SensorWidget(
sensor = sensor,
modifier = Modifier.animateItemPlacement()
)
}
}
animateItemPlacement() — плавная анимация при добавлении/удалении виджетов. Без key — Compose будет полностью перерисовывать список при каждом обновлении.
Числовые виджеты и частые обновления. MQTT может слать данные раз в секунду. Обновлять UI с такой частотой — расход батареи. Throttle на уровне Flow:
temperatureFlow
.throttleLatest(1000) // не чаще раза в секунду
.collect { updateWidget(it) }
throttleLatest вместо debounce — показывает последнее значение за период, а не ждёт паузы.
Исторические данные: загрузка и кеш
При открытии экрана нужны исторические данные за последние N часов для мини-графиков. Загружать все данные разом при открытии дашборда — плохо, если устройств много.
Паттерн: загружать историю лениво, по мере появления виджетов в viewport. LazyColumn/LazyGrid + LaunchedEffect при первом показе виджета:
@Composable
fun SensorWidget(sensor: SensorWidgetData, viewModel: DashboardViewModel) {
LaunchedEffect(sensor.id) {
viewModel.loadHistory(sensor.id, hours = 1)
}
// Показать skeleton пока данные грузятся
val history by viewModel.getHistoryFlow(sensor.id).collectAsState(emptyList())
MiniChart(data = history)
}
Кешировать историю в Room с TTL: данные старше 5 минут — запросить с сервера заново. Свежие — отдать из кеша без сетевого запроса.
Настройка дашборда пользователем
Перетаскивание виджетов, добавление/удаление сенсоров — опциональная, но ценная функция. На Android: ItemTouchHelper для RecyclerView или ReorderableItem из compose-reorderable библиотеки. Порядок виджетов сохранять в SharedPreferences или DataStore.
Реализация дашборда с realtime обновлением, историческими графиками и адаптивной сеткой: 4–6 недель. Стоимость рассчитывается индивидуально.







