При розробці мобільного додатку для моніторингу IoT-датчиків ми зіткнулися з задачею: як дати користувачеві гнучко налаштувати пороги спрацьовування, але при цьому не перевантажити інтерфейс? Рішення — комбінація Range Slider та профілів оповіщень. Ми реалізували модуль під ключ для iOS та Android, і тепер ділимося технічними деталями. За 5 років ми випустили понад 50 таких модулів для різних галузей — від розумного дому до промислових систем. Інженери оптимізували UI так, щоб користувач міг налаштовувати до 20 датчиків за 3 хвилини, а не витрачати годину на заповнення форм.
Які проблеми вирішує налаштування порогів?
Порогові значення — границі норми для датчика: вище +28°C — попередження, нижче +5°C — критично, надіслати push. Користувач налаштовує ці границі в додатку, сервер зберігає їх і генерує алерти при виході за межі. Задача виглядає простою, але потребує акуратного UX та надійної синхронізації з бекендом. Типові помилки: занадто часті запити при перетягуванні повзунка — до 100 запитів за секунду, втрата даних при збої мережі, неінтуїтивний інтерфейс. Наша реалізація знижує кількість звернень до підтримки на 30%.
Як працює гістерезис і чому він важливий?
Гістерезис запобігає хибним спрацьовуванням при коливаннях значення навколо порога. Наприклад, якщо температура коливається навколо +25°C, гістерезис у 0.5°C задає мертву зону: алерт спрацює лише при стійкому перевищенні. Реалізується простою перевіркою: if (value > threshold + hysteresis) >> alarm. В інтерфейсі можна додати окремий слайдер для гістерезису. Це особливо корисно для параметрів із природними флуктуаціями, таких як вологість або тиск.
Як вибрати компонент для введення порогів?
Числові поля введення — поганий вибір для порогів. Користувач не пам'ятає діапазон параметра, не бачить контекст. Краще — Range Slider із відображенням поточного значення сенсора на тій самій шкалі.
На Android Compose — кастомний RangeSlider або Slider із Material3. Стандартний RangeSlider із Material3 підтримує два повзунки:
var thresholds by remember { mutableStateOf(sensor.minThreshold..sensor.maxThreshold) } Column { Text("Температура: ${sensor.currentValue}°C") Text("Допустимий діапазон: ${thresholds.start.roundToInt()}°C — ${thresholds.endInclusive.roundToInt()}°C") RangeSlider( value = thresholds, onValueChange = { thresholds = it }, valueRange = sensor.absoluteMin..sensor.absoluteMax, steps = 0, onValueChangeFinished = { viewModel.updateThresholds(sensor.id, thresholds.start, thresholds.endInclusive) } ) } onValueChangeFinished — відправляти на сервер лише після відпускання повзунка, не при кожному русі. Інакше — шквал API-запитів при перетягуванні.
Поточне значення сенсора на шкалі слайдера — показати як вертикальну позначку. Через Canvas поверх слайдера: обчислити X-позицію за (currentValue - min) / (max - min) * sliderWidth.
На iOS (SwiftUI) аналогічна реалізація з використанням RangeSlider із SwiftUI Labs (або кастомного):
struct ThresholdSlider: View { @Binding var minThreshold: Double @Binding var maxThreshold: Double let currentValue: Double let range: ClosedRange<Double> var body: some View { VStack { Text("\(currentValue, specifier: "%.1f")°C") RangeSlider( value: $minThreshold, bounds: range, step: 0.5, onEditingChanged: { editing in if !editing { viewModel.updateThresholds(min: minThreshold, max: maxThreshold) } } ) } } } Типи порогів та їх конфігурація
Різні параметри потребують різних конфігурацій:
| Параметр | Логіка | Приклад |
|---|---|---|
| Температура | Нижня + верхня границя | +5°C … +25°C |
| Рух | Тільки булевий тригер | Виявлено/ні |
| Рівень CO2 | Тільки верхня границя | > 1000 ppm |
| Тиск | Нижня + верхня + швидкість зміни | < 950 або > 1050 hPa |
Не намагайтеся зробити один універсальний компонент для всіх. Краще набір спеціалізованих: BooleanThreshold, SingleBoundThreshold, RangeThreshold. Різні екрани налаштування для різних типів датчиків.
Чому важливе оптимістичне оновлення?
Пороги зберігаються на сервері та застосовуються на сервері при обробці телеметрії. Мобільний додаток — лише UI для їх налаштування.
При відкритті екрана: завантажити актуальні пороги з сервера, показати. При зміні: зберегти локально (оптимістичне оновлення), відправити на сервер, при помилці — відкотити до попереднього значення.
fun updateThreshold(deviceId: String, min: Float, max: Float) { val previous = _thresholds.value // Оптимістичне оновлення _thresholds.update { it.copy(minValue = min, maxValue = max) } viewModelScope.launch { val result = repository.saveThreshold(deviceId, min, max) if (result.isFailure) { // Відкат _thresholds.value = previous _events.emit(UiEvent.ShowError("Не вдалося зберегти налаштування")) } } } Оптимістичне оновлення робить UI чуйним — користувач не чекає відповіді сервера. Відкат — захист від втрати даних при мережевій помилці. За нашою статистикою, це знижує кількість звернень до підтримки на 30%.
Порівняння стратегій синхронізації
| Стратегія | Час відгуку | Консистентність | Складність реалізації |
|---|---|---|---|
| Оптимістична | Миттєве | Середня (можливий відкат) | Низька |
| Песимістична | Залежить від мережі | Висока | Середня |
| Гібридна | Швидке | Висока | Висока |
Оптимістична стратегія дає миттєвий зворотний зв'язок, але потребує механізму відкату при помилці. Песимістична гарантує консистентність, але блокує UI на час запиту. Ми використовуємо гібридну: локально зберігаємо останнє успішне значення, показуємо його, а фоновий потік періодично звіряє з сервером. Це забезпечує баланс швидкості та надійності.
Сповіщення: як не пропустити алерт?
Алерти приходять через push (FCM/APNS). Payload сповіщення має містити device_id, parameter, current_value, threshold_exceeded — щоб додаток міг відкрити потрібний екран при тапі на сповіщення.
На Android: setNotification() у FCM payload лише якщо додаток у фоні. Для foreground — FirebaseMessagingService.onMessageReceived() із локальним NotificationManager. Різні канали (NotificationChannel) для попереджень та критичних алертів — різні звуки та пріоритети.
Типові помилки при реалізації налаштування порогів:
- Занадто часті запити при перетягуванні повзунка (до 100 запитів/сек)
- Відсутність гістерезису (хибні спрацьовування кожні 5 хвилин)
- Неправильна обробка офлайн-режиму (втрата змін)
- Ігнорування різних типів датчиків (один компонент на всі)
Що входить у роботу
- Аналіз вимог і прототипування екрана налаштування.
- Розробка UI-компонентів (Range Slider, профілі, індикатори).
- Реалізація синхронізації з сервером (REST/GraphQL).
- Інтеграція push-сповіщень (FCM/APNS) з каналами та Deeplink.
- Написання документації та unit/snapshot-тестів.
- Код-рев'ю та підтримка при релізі в App Store і Google Play.
Результат — модуль під ключ, готовий до інтеграції у ваш додаток. Ми гарантуємо якість завдяки 5+ рокам досвіду в мобільній розробці IoT-рішень і понад 50 успішних проектів. Зв'яжіться з нами: оцінимо проект за 1–2 дні. Замовте розробку модуля прямо зараз.







