Групування IoT-пристроїв за зонами/кімнатами у мобільному додатку
Коли пристроїв у системі більше двадцяти — без групування додаток перетворюється на плоский список ламп, датчиків та реле вперемішку. Користувач не знаходить потрібний пристрій, управління замедлюється, скарги ростуть. Групування за зонами та кімнатами — не косметика, а обов'язковий архітектурний елемент для будь-якого IoT-додатка від середнього масштабу.
Де повинні жити дані про групування
Перше питання, яке визначає всю архітектуру: де зберігається відповідність «пристрій → кімната → зона»? Два варіанти — на сервері або локально на пристрої.
Локальне зберігання (SharedPreferences, AsyncStorage, Hive) працює до моменту, коли користувач встановить другий телефон або передасть управління іншому члену сім'ї. Групування теряется. Синхронізація через iCloud/Google Drive — окрема біль з конфліктами версій.
Правильний варіант: іерархія на бекенді. Структура: home → floor → zone → room → device. Кожен рівень — окремий запис у БД з parent_id та position (для сортування). Пристрій може бути прив'язаний лише до однієї кімнати, але кімнати можна об'єднувати в довільні зони (наприклад, «Перший поверх» та «Зона дитини» можуть перетинатися).
CREATE TABLE locations (
id UUID PRIMARY KEY,
home_id UUID NOT NULL,
parent_id UUID REFERENCES locations(id),
type VARCHAR(20) CHECK (type IN ('floor','zone','room')),
name VARCHAR(100),
position INTEGER DEFAULT 0
);
CREATE TABLE device_locations (
device_id UUID NOT NULL,
location_id UUID NOT NULL,
PRIMARY KEY (device_id, location_id)
);
Зв'язок many-to-many між пристроєм та локаціями для сценаріїв вроде «датчик руху вважається частиною та коридора, та зони безпеки».
UI групування: Що працює на практиці
На Flutter використовуємо SliverList з SliverAppBar для кожної групи — це дає плавний scroll з прилипаючими заголовками кімнат без втрати продуктивності. ExpansionTile для приховування/розкриття кімнат. Drag-and-drop для переназначення пристроїв — через ReorderableListView або пакет flutter_reorderable_list.
На React Native — SectionList з stickySectionHeadersEnabled. Для DnD використовуємо react-native-draggable-flatlist або Reanimated 3 з жестами через GestureHandler. Не використовуємо стандартний ScrollView з ручним вичисленням позицій — це гарантований перформанс-кошмар на Android.
Важливий UX-момент: статус групи (все включено / частково / все вимкнено) повинен агрегуватися на клієнті з кешу станів пристроїв, а не запрошуватися окремим API-викликом. Інакше при відкритті екрану «Гостиная» — 20 паралельних запитів, 200ms lag, видимий джиттер на iOS.
Агрегацію робимо через Riverpod StateProvider (Flutter) або Zustand selector (React Native): пересчет статусу групи відбувається реактивно при оновленні будь-якого пристрою з групи.
Синхронізація у реальному часі
Користувач переіменував кімнату або переместив пристрій — змина повинна відобразитися на всіх залогінених пристроях. Реалізуємо через WebSocket-канал з повідомленнями типу:
{ "event": "location_updated", "location_id": "...", "changes": { "name": "Спальня 2" } }
{ "event": "device_moved", "device_id": "...", "from_location": "...", "to_location": "..." }
Клієнт оновлює локальний кеш (flutter_riverpod Notifier / Zustand store) без повної перезагрузки списку. Критично для многопользовательских сценаріїв — сім'я управляє одним домом з різних телефонів.
Що ще варто врахувати
Іконки та кольори кімнат — користувацькі, зберігаються у locations.metadata (JSONB). Швидкий доступ до улюблених пристроїв — окремий разділ favorites зі своїм порядком, незалежне від кімнатної іерархії. Пошук за іменем пристрою — індекс на devices.name + розширення pg_trgm для нечіткого пошуку.
Процес та терміни
Проектування іерархії та API — 3–5 днів. UI з групуванням та базовим DnD — 2 тижні. Реалтайм-синхронізація, мультипользовательские сценарії, пошук — ще 1.5–2 тижні. Усього базова функціональність — 4–6 тижнів залежно від платформи та складності іерархії.







