Один из главных вопросов при проектировании приложения — выбор стейт-менеджера. Его реализация должна: Позволить отделить бизнес-логику от логики отображения. Иметь отказоустойчивый код. Расширять понятным и простым способом функциональность проекта при внедрении новых фич. Моя коллега Кристина Зотьева уже рассказывала, как подружить Elementary и Bloc для управления локальным состоянием.  В этой статье поговорим об управлении глобальным состоянием. Меня зовут Владимир Деев, я Flutter-разработчик компании Surf. Расскажу, как наиболее продуктивно связать Redux и Elementary и «подружить» Redux с асинхронными операциями. Упростим задачу: не будем глубоко закапываться в структуры данных и красоту отображения на экране. Зато подробно рассмотрим реализацию Redux в «товариществе» с Elementary.  Представим себе простейшее приложение:  При нажатии на кнопку «плюс» программа загружает и отображает случайным образом выбранную фотографию собаки.  При перезапуске приложения загруженные данные остаются на экране без загрузки по сети.  При нажатии на «крестик» все фотографии должны удалиться. Код приложения на гитхабе Давайте рассмотрим, каким образом будет работать приложение. State — хранилище данных приложения. State является иммутабельным, нам доступно только создание нового стейта. Action — триггер изменения state.  Чистый Redux не умеет работать с асинхронностью. А так как у нас есть сетевые запросы, которые нужно обработать в пространстве самого Redux, мы будем использовать redux_epics в качестве middleware-составляющей. Epics middleware — промежуточная часть между reducer и action. Принимает на вход action, обрабатывает сетевые запросы и запускает следующий action. Reducer — «командный пункт» Redux-архитектуры. Принимает actions непосредственно от middleware или напрямую из приложения и работает со state путём создания нового стейта с новыми данными. Подключим основные зависимости: Elementary Redux Redux_epics Это потребуется непосредственно для реализации задачи. Разберёмся, какие данные нужны. В качестве источника данных будем использовать ресурс https://dog.ceo/dog-api/. Как можно увидеть из документации, JSON с ответом от сервера содержит message, в котором хранится url картинки с собакой, а также поле status. Поэтому опишем два класса: DogData будем использовать для хранения данных, DogDTO — для обмена данными между слоем данных и сетевым слоем. Приступим к разработке Redux-части. Сначала опишем стейт, хранилище данных. Как видим, данные хранятся в виде неиммутабельного списка с пустым листом в качестве значения по умолчанию. Давайте будем хранить здесь также информацию об ошибке запроса данных. Реализуем первый action для загрузки сетевых данных. Создаём класс RequestLoadingAction с миксином. Для контроля за выполнением сетевых запросов будем использовать completer как систему сигналов для Elementary-части. Чтобы обработку completer не прописывать заново в каждом классе action, по которому будет обрабатываться сетевой запрос, вынесем код в миксин. Кстати, этот хитрый товарищ нам потом немного скрасит хмурое бремя программиста при написании тестов для Redux-составляющей приложения, и мокировать completer не придётся. Преобразуем Stream входящего action в Stream исходящего action: либо в action ошибки, в который передаём dio error, либо в action добавления данных, в который передаём полученный по сети объект.  Чтобы не писать один громоздкий Epic, в котором обрабатываются все приходящие в middleware экшены, используем combineEpics. В списке будут храниться все Epics: небольшие, хорошо тестируемые юниты, привязанные каждый к конкретному экшену. Также здесь сохраняем список данных о картинках в локальном хранилище и завершаем комплитер. Не забываем добавить новый экшен, который будет обрабатываться уже в reducer. А также экшен для ошибки. Работа со State в Reducers. Это святая святых Redux.  Здесь всё просто: получили ошибку — возвращаем новый стейт, в который добавляем текущую ошибку. Получили новые данные — возвращаем стейт с новыми данными. Дело за малым: сообщить приложению о том, что здесь есть Redux со своим state, middleware и reducers. Всё: Redux внедрен в приложение. Осталось самое интересное — «попросить» Elementary с ним работать. Подробно про пакет Elementary мы писали в статьях: Elementary: новый взгляд на архитектуру Flutter-приложений Как устроен Elementary Elementary состоит из трех слоев:  Model,  WidgetModel,  Widget.  Давайте по шагам подключим собранный ранее Redux-инструмент к Model. Потом передадим получаемую от Redux информацию через WidgetModel к презентационному слою, а также заставим работать этот механизм в обратном направлении: от Widget к Model. В Model необходимо на этапе инициализирования добавить подписку на изменения стейта. Теперь можно отслеживать изменения списка с данными и ошибки в работе сетевого запроса. Любое из этих событий можно обработать как нам угодно.  Чтобы Redux реагировал на изменения в UI, достаточно в методе модели вызвать dispatch и отправить в него соответствующий экшн: Взаимодействие между менеджером состояний и UI готово:  Пользователь нажимает кнопку на экране. Через связку screen-WidgetModel-model запускается механизм взаимодействия с Redux: в middleware загружаются данные из сети. Через action они передаются в reducers, который и создает новый стейт с новыми данными. В Model срабатывает подписка о том, что стейт изменился. Новые данные вносятся в ValueNotifier, изменения в котором проходят через WidgetModel и слушаются на экране. Плюсы: За счёт связки Redux + Elementary управляем ребилдом только нужных элементов. Redux state — единственный источник правды. Достигнута иммутабельность state: доступно только копирование текущего состояния данных. Это позволяет исключить незапланированное изменение текущих данных. Благодаря выбранной архитектуре можно легко проследить, какое действие с данными к каким результатам приводит. При разработке новых features можно легко добавить необходимые поля в state, необходимые actions и обработку соответствующим reducer. Всё, что касается загрузки и обработки данных, управляется Redux.   Минусы: Большое количество бойлерплейт-кода даже для одного state. Если в приложении планируется несколько несвязанных между собой источников данных, то писать несколько redux state, reducers и большого количества actions, конечно же, будет проблемой. Ссылки: Пакет Elementary Обзор Elementary Тестовое приложение на гитхабе Как устроен Elementary Как подружить Elementary и BLoC Элементарное тестирование, или тестирование Elementary Больше полезного про Flutter — в телеграм-канале Surf Flutter Team. Публикуем кейсы, лучшие практики, новости и вакансии Surf, а также проводим прямые эфиры. Присоединяйтесь! Больше кейсов команды Surf ищите на сайте  >>