Доброго времени суток, дорогие читатели! Меня зовут Сурен, и я разработчик.Поскольку моя предыдущая статья о том, как бекендер в мобильную кроссплатформу лез, не утонула в минусах, я решил продолжить делиться своим опытом познания данной замечательной технологии =) Написано немало статей про MVVM, его реализацию на различных технологиях и на Flutter, в частности. Но мне они давались с трудом, и не было понимания, как оно в итоге работает. Возможно, сказывается особенность восприятия “Бекендера” =) Поэтому, если среди читателей есть люди с похожим складом ума, возможно эта статья поможет и Вам понять, что такое MVVM и как его реализовать на Flutter простым способом.  MVVM (Model-View-ViewModel) — шаблон проектирования архитектуры приложения. Пришел на смену шаблону MVC|MVP (Model-View-Controller/Presenter). Актуален для платформ, в которых присутствует концепция «связывания данных», позволяющая связывать данные с визуальными элементами в обе стороны. На схеме это выглядит так: Где Model представляет из себя слой бизнес-логики, который включает описание фундаментальных данных, необходимых для работы приложения, и логику работы с ними. View является графическим интерфейсом. Подписан на изменения ViewModel, а при действиях с интерфейсом вызывает команды ViewModel для изменения данных в Модели. ViewModel же с одной стороны — абстракция Представления, а с другой — обёртка данных из Модели, подлежащих связыванию. То есть она содержит Модель, преобразованную к Представлению, а также команды, которыми может пользоваться Представление, чтобы влиять на Модель. Для обеспечения “связывания данных” я использую связку Provider+ChangeNotifier. Provider представляет собой смесь Инъекции Зависимостей (Dependency Injection или DI) и управления состоянием, а ChangeNotifier позволяет отслеживать изменения ViewModel и перестраивать наш UI. Построим простейший ToDoList на данном подходе и попутно будем разбираться, как он работает.  Внимание! В качестве примера для наглядности и удобства я буду реализовывать каждый виджет в одном файле, то есть и ViewModel, и View(Виджет) и его Model. Но на боевых проектах, особенно если вы работаете в команде, эти сущности нужно разносить по разным файлам.   Для начала создадим новый проект приложения на flutter. Перед нами откроется чистый проект, представляющий из себя приложение счетчик или кликер. В main.dart у нас реализован основной метод main(), запускающий приложение MyApp, которое состоит из 1 StatefullWidget - MyHomePage, реализующего счетчик нажатий. Приложение можно запустить и проверить, что все работает.  Но мы собрались делать ToDoList, следовательно пока оставляем main.dart. Создаем рядом новый файл todo_list_widget.dart, а в нем классы:  _ViewModel, реализующий ChangeNotifier,  ToDoListWidget, реализующий StatelessWidget,  _ModelState, в котором мы будем хранить состояние нашей модели.  В Dart символ “_” перед именем свойства/класса/метода делает его приватным (доступным только в пределах текущего файла .dart). Добавим файл todo_item.dart и реализуем в нем класс Модели ToDoItem. Цель — сделать некий список дел, так что свойства нужные нам в данном классе: name - наименование задачи (по совместительству ключ), done - флаг о выполнении. Также, чтобы у нас была возможность “редактировать” элементы списка, добавим метод copyWith. Наш класс модели будет выглядеть следующим образом:  Возвращаемся к основному.  В стейте мы будем хранить список элементов ToDo листа. Для обеспечения иммутабельности список у нас будет “final”, а для редактирования мы будем его копировать, поэтому добавим метод “copyWith”. В нашем случае отдельный стейт выглядит как “бойлерплейт“, но для наглядности и последующего масштабирования он полезен.  После этих манипуляций класс принял следующий вид:    Переходим к ViewModel, поскольку задача данного класса — быть прослойкой между интерфейсом и данными. С одной стороны мы должны в нем следить за изменениями модели и дергать перестраивание интерфейса, а с другой — должны иметь методы воздействия на эту модель. Добавим в него private свойство “_state” и дополним публичным сеттером и геттером. Геттер возвращает приватный стейт, а сеттер заменяет его новым значением и уведомляет прослушивающие виджеты при изменении стейта.  “notifyListeners” — метод в классе “ChangeNotifier”, уведомляющий виджетов-слушателей об изменении модели и дающий команду перестроиться.  Для реализации ToDo списка нам нужны следующие методы:    После каждого действия мы вызываем Сеттер стейта для того, чтобы уведомить об изменении слушателей и перестроить стейт. На первый взгляд основная логика готова, так что перейдем к View (виджету). Пришло время добавить в проект пакет Provider. Для этого можно либо отредактировать файл pubspec.yaml, добавив в него зависимость от пакета Provider, либо выполнив команду в консоли:flutter pub add provider — данная команда, собственно, и добавит зависимость в тот же файл и скачает пакет последней версии. Чтобы наш виджет мог реагировать на изменение стейта, для начала обернем его в ChangeNotifierProvider. В метод создания мы передаем нашу ViewModel, а в качестве ребенка — наш Виджет. Для таких оберток удобно делать статический метод create внутри виджета, который и инициирует ViewModel и передает ее в виджет ребенка. Теперь для получения доступа к ViewModel внутри билдера виджета, мы можем ее получить через context. Для этого мы и используем пакет Provider.  Метод watch<ClassName>() не только отдает нам через провайдер экземпляр модели, которая была инициализирована выше по дереву виджетов, но и добавляет текущий виджет в качестве слушателя изменений в модели. То есть, когда мы в модели вызываем метод notifyListeners, наш виджет-слушатель перестраивается, за счет чего и достигается “связывание данных” между моделью и представлением. Теперь набросаем простенький интерфейс, аналогичный базовому виджету MyHomePage, но с той лишь разницей, что в качестве тела виджета у нас будет список. В качестве списка будем использовать ListView.builder, поскольку он рендерит только те элементы, которые видны на экране, и в целом удобен.  В качестве элемента списка напишем виджет ToDoItemWidget, который отдаст нам CheckboxListTile (элемент списка с чекбоксом), обернутый в Dismissible (для реализации удаления свайпом), и будет пробрасывать 2 делегата: на удаление элемента из списка и на изменение состояния элемента списка (выполнено/не выполнено).    Для добавления элемента нам нужно сначала где-то взять название для задачи. Для этого напишем виджет AddTaskDialog. Он представляет из себя AlertDialog с текстовым полем и делегатом, который в случае успеха возвращает нам новенькую задачу, а также сообщает об ошибках через всплывающие уведомления.    Всё. Теперь можно собрать и посмотреть, что он делает =) Все заложенные нами кейсы работают =)  В дальнейшем можно прикрутить sqlite, добавить инициализацию и синхронизацию, и вот — простенький задачник готов (шутка). Я специально не затронул в данной статье редактирование задач. Если захотите попробовать повторить, Вы сможете это сделать сами =) Ссылка на репозиторий с данным примером. Я не утверждаю, что MVVM — это лучший шаблон проектирования для мобильных приложений на Flutter. Это пока первое, что я осознал и принял на вооружение после statefullWidget. Самое главное для меня — это его масштабируемость и применимость для достаточно больших приложений. Плюс он отвечает требованиям Чистой архитектуры. Только прошу, не рассматривайте мой пример как Архитектурный. Я показал в коде на примере приложения для чек-листа, как реализовать данный шаблон.