Что такое ViewModel
ViewModel — одна из трех составляющих шаблона проектирования архитектуры Model-View-ViewModel (MVVM). Согласно данной концепции, модель содержит логику работы с данными и их описание, представление (View) является графическим интерфейсом, а ViewModel связывает их.
В рамках НИР реализуется ViewModel для мобильного приложения, позволяющего искать и отображать рецепты.
Для чего нужна ViewModel
- Разделение бизнес-логики и интерфейса.
- Кеширование данных.
- Взаимодействие между несколькими экранами.
Что содержит в себе ViewModel
- Данные из запроса.
- Методы для запроса данных из сети.
- Список текущих объектов в обработке.
- Индикатор загрузки.
- Методы для отправки команд интерфейсу.
Как реализуется модель представления
Для удобства и возможности добавлять новые модели создан абстрактный класс BaseViewModel<T, E>, расширяющий ChangeNotifier. Он может уведомлять другие классы об изменениях (например, чтобы те перестроили UI). Базовая ViewModel содержит данные в виде типизированного (от Т) списка, а также методы для работы с ним: добавление, удаление, замена и так далее. Каждый метод имеет две вариации: с уведомление подписчиков и без него.
Кроме того, имеется переменная-флаг, отвечающая за состояние загрузки, и метод по заданию её значения и уведомления подписчиков.
Реализован механизм оповещения класса View о конкретном событии с помощью uiEventSubject. Эта переменная инициализируется с помощью PublishSubject<E>(), в которую ViewModel будет посылать события типа Е. Для того чтобы UI класс мог реагировать на эти события, определим специальный метод startUIListening:
final _uiEventSubscription = CompositeSubscription();StreamSubscription startUIListening(Function(E) listener)=> _uiEventSubscription.add(uiEventSubject.listen(listener));
Далее опишем реализацию конкретной модели представления для экрана со списком рецептов. Данный класс будет наследоваться от базовой ViewModel. Основной метод загрузки рецептов принимает строку с ключевыми словами и вызывает метод репозитория для выполнения запроса к серверу, а затем добавляет результат в хранилище данных. При этом запрос в сеть обрамляется изменением состояния загрузки, чтобы на интерфейсе отобразить индикацию загрузки. Ниже приведена общая схема основного метода ViewModel, скачивающего данные [1].
Future<void> loadData({String value, bool showLoading = true}) async {setLoading(value: true, notify: showLoading);final result = await dataRepository.getData(search: value,);if (result.result) {silenceClearItems();silenceAddRange(result.value as List<Data>);}setLoading(value: false);}
Здесь методы методы silenceClearItems и silenceAddRange имеют приставку, означающую, что они не уведомляют подписчиков о том, что данные изменились. Это делается для того, чтобы предупредить слишком частое обновление UI. Метод notifyListeners вызовется в заключительном setLoading, унаследованном от базового класса.
void setLoading({bool value, bool notify = true}) {loading = value;if (notify) {notifyListeners();}}
Кроме того, в модели представления содержится метод, который класс View вызывает при нажатии на карточку конкретного рецепта, чтобы открыть полную информацию. Для этого выполняются следующие шаги:
- Занесение id рецепта в processingIds, чтобы предупредить многократные клики.
- Возведение индикатора загрузки.
- Запрос в сеть.
- Снятие индикатора загрузки.
- Удаление id из processingIds.
- Отправка события в uiEventSubject.
Взаимодействие View с ViewModel
Класс View подписывается на изменения во ViewModel с помощью consumer из пакета Provider [2]. Это позволяет виджету обновляться всякий раз, когда модель представления шлет notifyListeners. В структуре дерева класса View добавляется проверка, по которой отображается индикатор загрузки при соответствующем значении в модели представления.
Кроме того, View подписывается на события UI, которые модель представления отправляет в uiEventSubject [2]. В нашем случае по этому событию будет открываться экран с полной информацией о рецептах.
Заключение
Таким образом, реализован базовый класс ViewModel и конкретная модель представления для рецептов. Данное архитектурное решение позволяет структурировать код и сделать его легко расширяемым. В дальнейшем работа может быть дополнена кешированием пользовательских запросов.
Источники
[1] Ogubuike R., Adib A., Orji R. Masa: AI-Adaptive Mobile App for Sustainable Agriculture //2021 IEEE 12th Annual Information Technology, Electronics and Mobile Communication Conference (IEMCON). – IEEE, 2021. – С. 1064-1069.
[2] Haider A. Evaluation of cross-platform technology Flutter from the user’s perspective. – 2021.