Для работы с проектом требуется Android Studio версии Arctic Fox (2020.3.1). Можно использовать встроенный JDK, либо установить AdoptOpenJDK 11.
Все зависимости будут скачаны при первой сборке проекта.
Структура проекта:
- система сборки - стандартый gradle
- подписывание release-сборки (в корень нужно добавить файл keystore_release.properties, который содержит данные keystore)
- используемый паттерн - MVVM
- интерфейс - Jetpack Compose
- l10n - стандартная Android (ресурсные файлы strings.xml), пример1 и пример2
- заставка - тема SplashTheme на момент старта приложения
- тема - стандартная MaterialTheme из Compose
- аналитика сбоев - Firebase Crashlytics (в проект androidApp нужно добавить файл google-services.json)
Визуально приложение состоит из двух экранов: список серверов и данные сервера. Последнее, в свою очередь, состоит из трёх экранов: список датчиков, история изменения активных датчиков и настройки сервера. Список серверов представляет собой список окружающих ble-серверов, анонсирующих ble-сервис с "нашим" кодом 95f78395-3a98-45a3-9cc7-d71cfded4f07 (CONF_SERVICE_UUID). У сервера выводится его имя, MAC-адрес и уровень сигнала. Список датчиков сервера представляет собой список с именем датчика, его состоянием (авария/норма/неопределённое) и коэффициентом (только у adc-датчика). Также датчик можно включить или выключить с помощью переключателя (изменить его активность). Также при старте этого экрана выполняется синхронизация времени на сервере. На экране истории выводится список последних пяти изменений активных датчиков. Событие истории состоит из времени изменения и списка активных датчиков. На экране настроек выводится системное время сервера и настройки регистрации adc-датчика, перед просмотром/редактированием настроек необходимо ввести пароль.
Данные с сервера считываются при старте экрана. Для перечитывания используется "swipe to refresh" (значение коэффициента adc-датчика обновляется автоматически через ble-notify).
Архитектурно приложение разделено на 3 слоя:
- визуальная часть и навигация (папка ui_compose и файл MainActivity)
- вьюмодели (файлы ServersListViewModel и ServerViewModel)
- домен (папки domain и services)
Домен состоит из классов, представляющих сервер, датчик, событие истории и настройки.
Сервисы BleConn/BleServerConn изолируют работу с BT-адаптером. Для работы с BT используется мультиплатформенная библиотека Kable. Сервис BleConn запускает сканирование ble-устройств в отдельной корутине и "выдаёт" результаты через поток servers (StateFlow). Методы сервиса BleServerConn реализованы как suspend-функции. Также сервис содержит поток coeff (Flow) со значениями коэффициента adc-датчика, получаемые через ble-notify. Для тестов реализованы "заглушки" BleConnStub/BleServerConnStub, которые работают без наличия BT-адаптера и возвращают случайные данные. Оба сервиса зависят от скоупа владеющего объекта (вьюмодели) - работающие корутины автоматически завершаются когда "завершается" владеющий объект (удаляется вьюмодель).
Вьюмодели работают с доменом и "выставляют наружу" потоки (StateFlow) для видов. Объект ServersListViewModel используется экраном со списком серверов. Функционально класс ServersListViewModel повторяет BleConn. Методы BleConn запускаются в рамках скоупа вьюмодели. Объект ServerViewModel используется тремя экранами сервера (список датчиков, история и настройки) - ведь это один сервер, и подключение к нему тоже одно. Класс предоставляет отдельные потоки данных для этих экранов (sensors, history, conf и time) и методы, обновляющие эти потоки (reloadSensors, reloadHistory, reloadConf и reloadTime) и задающие новые значения (setEnabled, setConf и syncTime). Также при обновлении списка датчиков идёт подписка на поток BleServerConn.coeff, новые значения в котором "обновляют" поток sensors (при выходе из экрана необходимо остановить подписку через метод stopObserveSensors). Методы класса запускают корутины (с вызовом методов BleServerConn) в рамках скоупа вьюмодели.
Визуальная часть реализована через Jetpack Compose. Composable-функции подписываются на потоки вьюмоделей через collectAsState, эффекты (запуск поиска серверов, загрузка списка датчиков, истории и настроек) вызываются через DisposableEffect. Навигация - стандартная (см. функцию RootWithNavigation). Выделены два роута: список серверов и описание сервера (вложенный роут). Последний содержит три роута для сервера: список датчиков, история и настройки. Время жизни вьюмоделей привязывается ко времени жизни роута (времени жизни composable-функции/экрана). Экраны сервера "используют" общую вьюмодель (через привязку к родительскому роуту).