о Redux архитектуре во Flutter приложениях

Не так давно вышла статья о чистой архитектуре во Flutter. Хочу осветить тему немного под другим углом и развить тему управления глобальным состоянием с помощью Redux.

И немного о себе: я занимаюсь созданием коммерческих продуктов около 10 лет, из которых на Flutter почти 2 года и успел попробовать все известные стейт-менеджеры. Какие-то вызывают нейтральные воспоминания — BLoC, Provider, глобальный класс-блок со своими стримами, а какие-то и негативные — MobX.

В итоге для себя я остановился на Redux для глобального состояния и библиотек для реализации структуры приложения:

  • built_value
  • built_collection
  • rxdart (по необходимости)
  • flutter_simple_dependency_injection (или dioc)
  • built_redux

Это мой минимальный набор библиотек для реализации проектов любого уровня.

А теперь по шагам

Общая структура приложения

Общая структура приложения

Папки в корне все стандартные, создаются автоматически, но есть дополнительные:

  • go — папка для запуска супер облегченной десктоп версии приложения с помощью hover. Об этом уже была моя статья. Все платформонезависимые плагины работают, недостающие пишутся на GoLang. Плюсы, помимо того, что ресурсы компьютера тратятся по минимуму и возможности быстрого просмотра верстки под различные размеры, отмечу возможность просмотра локальной базы sqlite без лишних телодвижений, если таковая используется. Просто открываем файл любимой IDE, например, SQLiteStudio. Создается стандартно — hover init, получаем готовое десктоп-приложение
  • build.yaml — для генератора built_valueбудемсоздаватьспомощьюнегомодельки
  • analysis_options.yaml — файл для настроек линтера. Мастхев.
  • scripts — здесь лежат различные скрипты для запуска эмуляторов/десктоп/ховер/веб версий или вспомогательных операций: запуск генерации файлов, форматирование кода, создание локальных настроек, проверка версии Flutter, чтобы вся команда работала на одной версии. Часть команд запускается из пре-тасок. Пример: prepare_app — проверкаверсии Flutter, prepare_app_hover — проверка версии и запуск самого hover. Так же есть файлы докера для запуска сервера центрифуги, который будет помогать отладку приложения (немного про отладку приложения здесь) и скрипт для запуска dartfix — очень помогает справиться с легаси кодом
  • application_bundle — папка с настройками. Например, здесь можно расположить различные JSON файлы с настройками под различные раннеры — сборки в зависимости от назначения приложения — «лайт» версия приложения, с урезанным функционалом, полная версия и т.д.
Пример пре-тасок
Пример пре-тасок
Пример скрипта

Структура Flutter-приложения

В самой папке lib я обычно создаю следующие файлы и папки:

  • domain — здесь хранится само ядро приложения: наборы экшенов, базовые классы, эпики, миддлвары, модельки, редьюсеры, селекторы и классы состояния
  • tools — различные утилиты
  • di — класс, реализующий инверсию зависимостей
  • features — UI часть, например, реализация страницы пользователя
  • services — различные сервисы, например, сервис для сохранения локальных настроек, сервис логгера
  • app — базовые классы для запуска приложения. Здесь я располагаю MaterialApp или CupertinoApp
  • app_routes.dart — класс с названиями роутов для навигации

Domain

  • models/enums

раньше я создавал модели в ручном или “полуавтоматическом” режиме с помощью генератора моделей, например, quicktype. Удобно, легко позволяет сгенерировать модельки из JSON файла с сериализацией/десериализацией, но т.к. нет иммутабельности и возможности проставить начальные значения при десериализации, отказался от этого подхода в пользу генератора built_value, у которого есть все необходимое:

Пример реализации класса-модели
  • actions

созданные built_redux генератором модели экшенов для быстрого старта Redux-приложения

Пример реализации класса-модели экшена
  • middlewares

набор миддлваров, собирается в единое приложение так же библиотекой built_redux

Пример реализации класса-миддлвара
  • epics

набор эпиков для асинхронной обработки экшенов. Так же built_redux. В основе обработки стандартные стримы и тут при желании и необходимости можно применить библиотеку rxdart

Пример реализации класса-эпика
  • reducers

набор редьюсеров, меняющих наш глобальный стейт

Пример реализации класса-редьюсера
  • states

набор состояний Redux. Здесь есть один общий класс-состояние — AppState, хранящий в себе другие состояния: состояние пользователя, состояние локальных настроек и т.д. Собирается так же built_redux

Пример реализации класса-состояния

DI

Инверсия зависимостей для различных сервисов. Для быстрого старта подойдет flutter_simple_dependency_injection

Пример реализации инверсии зависимостей

Features

Фичи — наборы папок, зависящих от домена и ничего не знающие про соседние фичи. Состоят из нескольких папок — blocs, components, widgets, tools. Точка входа — как правило один виджет в папке widgets по названию папки. Каждый виджет зависит от своего блока(BLoC) — обычный класс, который стримит данные в этот виджет. Главный принцип — один блок — один виджет. Жизненный цикл которого для StatefulWidget’а

  • создание в initState
  • работа — стриминг в StreamBuilder’ов
  • уничтожение в dispose

При необходимости создаются дополнительные компоненты в папке components — более мелкие виджеты необходимые для отрисовки виджета-фичи, которые так же могут быть с такой же структурой папок

BLoC

Класс с бизнес-логикой для виджета-фичи. Наследуется от базового абстрактного класса, который имеет доступ к домену и di-контейнеру. Содержит методы, поля для наследуемых классов-блоков

Пример базового абстрактного класса BLoC

Классы-блоки виджетов наследуются от базового класса BaseBloc и стримят нужные состояния в виджеты. Я создаю вручную сабжекты и нужные стримы

Пример BLoC-класса

 Здесь:

  1. объявление сабжектов и стримов
  2. закрытие подписок, сабжектов
  3. инициализация класса. Создание сабжектов и подписка/подписки на изменения нужных состояний, например, профиля. Слушаем изменение, делаем какие-то манипуляции (фильтрация, дебаунс, троттл, и т.д.), добавляем в сабжект (либо же, конечно, если не нужны дополнительные манипуляции со стейтом, то стримим сразу nextSubstate)

И дополнительные методы для какой-то логики изменения состояния виджета либо для изменения домена путем вызова экшенов

Пример метода для вызова экшена

Виджеты

Отрисовываем изменение состояния нужного стейта с помощью StreamBuilder:

Отрисовка изменения состояния

Заключение

Описанный мной подход, конечно же, как и все остальные подходы по организации структуры кода своего приложения имеет как плюсы, так и минусы.

Минусы:

  1. трата времени на генерацию файлов. Если требуется внести правки в один файл, перегенерятся и другие. Для 300 классов время может занимать до 2х минут
  2. многословность Redux и, как правило, много бойлерплейт

Плюсы:

  1. иммутабельность, сериализация, дефолтные значения с built_value “из коробки”
  2. быстрое обучение новичков из мира фронтенда знающих Redux
  3. почти безболезненное включение/выключение фич
  4. независимая работа членов команды над задачами. Например, на моей текущей работе в команде 13 Flutter разработчиков, абсолютно не мешающих друг другу благодаря вышеописанной структуре приложения
  5. доменная часть, независимая от UI — как пример чистой архитектуры, что дает возможность поместить ее (целиком Redux состояние со всеми миддлварами и эпиками, выполняющими много работы в бекграунде) в свой отдельный изолят

И хочу добавить, что простых проектов не бывает. Бывает и пет-проект разрастается до коммерческих продуктов. И тогда после неправильно спроектированной архитектуры нормальная работа может стать невозможной. У меня есть проект, так же на Flutter, который я ради интереса попробовал написать через архитектуру MobX. Проект разросся. Работать стало, мягко говоря, некомфортно, пришлось все переписывать на Redux.  

Целью этой статьи было обратить внимание начинающих или опытных разработчиков на Redux архитектуру, которая хорошо зарекомендовала себя на очень большом коммерческом продукте с десятками тысяч ежедневных пользователей. Пережила и выдержала приходы/уходы коллег, внедрение/удаление различных фичей.

Ссылка на статью на Хабре

(Visited 17 times, 1 visits today)

Добавить комментарий