Всем привет! Я Айдар Мавлетбаев, Flutter-разработчик в AGIMA. В самом начале любого проекта очень важно выбрать архитектурный паттерн, ведь именно это может спасти ваш проект на более поздних этапах. В статье сравним архитектуры BLoC и MVC, подробно рассмотрим библиотеку GetX, выделим ее плюсы и минусы. В этом нам помогут два простых примера: это функция авторизации и List Data.
Немного об архитектуре
Существует огромное количество архитектурных паттернов: Clean Architecture, DDD, MVC, BLoC и так далее. Обычно архитектуру выбирают, исходя из сложности приложения, бюджета, времени и других факторов. Здесь важно понимать, что от подхода, который мы выберем в начале разработки, зависит удобство поддержки уже готового приложения в будущем.
Каждый паттерн имеет свои плюсы и минусы. Но в статье мы рассмотрим именно MVC и BLoC.
Про BLoC
BLoC (Business Logic Component) — это архитектура управления состоянием, которая отделяет бизнес-логику от UI-слоя в разработке программного обеспечения. Паттерн BLoC часто используется в разработке приложений на Flutter для управления состоянием в реактивном и эффективном режиме.
BLoC разделяет приложение на UI, Bloc и Data, а сам он состоит из трех ключевых компонентов:
Событие. Это входные данные, которые вызывают изменение состояния приложения. События могут быть как пользовательскими взаимодействиями (например, нажатие кнопок), так и системными событиями (например, сетевые запросы).
Bloc. Это компонент бизнес-логики, который обрабатывает события и обновляет состояние приложения. Bloc отвечает за управление состоянием приложения и взаимодействие с UI-слоем для обновления представления.
Состояние. Представляет текущее состояние приложения. Состояние может быть простым логическим значением или сложной структурой данных.
Менеджер состояния BLoC прослушивает события и обновляет состояния приложения. Затем UI-слой прослушивает состояние и обновляет представление при изменении состояния.
Про MVC и GetX
Здесь схема похожая. Паттерн MVC (Model-View-Controller) тоже разделяет приложение на три слоя:
Модель (Model) — это компонент, который отвечает за хранение и обработку данных приложения. Модель представляет собой набор классов, которые описывают структуру данных и предоставляют методы для работы с ними.
Вид (View) — отвечает за отображение данных на экране. Вид представляет собой набор виджетов, которые отображают данные, полученные из модели.
Контроллер (Controller) — отвечает за взаимодействие между моделью и видом. Контроллер обрабатывает события, генерируемые пользователем, и изменяет состояние модели в соответствии с этими событиями. Затем контроллер обновляет вид, чтобы отобразить изменения.
Ну а GetX — это микрофреймворк, в котором есть всё для разработки полноценного MVP, Routing, State Manager, Localizations и т. д. По своей сути, это усовершенствованный Service Locator 2.0, который дает возможность не использовать Context.
BLoC vs MVC
Так выглядит файловая структура BLoC и MVC:
В BLoC мы видим четкое разделение на три слоя: в Data лежат модели и репозитории, Bloc содержит бизнес-логику, ну и сверху всего UI.
В MVC нет репозиториев, потому что на небольших проектах всю логику Local Data или Remote Data можно прокинуть сразу в контроллер, а здесь их два.
BLoC vs GetX
Теперь давайте сравним эти подходы на двух простых примерах — авторизации и List Data, — каждый отдельно на BLoC и GetX. Это типичный кейс для большинства Flutter-приложений в заказной разработке.
Первый пример: функция авторизации
BLoC:
AuthEvent. В этом классе прописаны все события, которые будут вызываться в UI — Sign up, Sign in и Sign out. Здесь ничего сложного.
AuthBloc. Выполняет логику события, возвращает состояние.
Здесь обрабатывается Event, который мы вызывали, появляется зависимость от To do и мы задаем какую-то переменную.
AuthState. Класс состояния нашего UI.
State — обычный глобальный класс. Он один, и здесь уже есть конструктор с зависимостью. Как и в AuthBloc, мы задаем какие-то изменения в наших переменных User или Failure.
GetX:
AuthController. Здесь один класс, без строгого разделения на слои, как в BLoC. На картинке выше видно, что запись минимальная, а результат такой же.
BLoC:
AuthPage. Страница авторизации. Здесь используется несколько Bloc-виджетов, но основной — BlocConsumer.
При помощи BlocProvider мы получаем зависимость в виде блока. А уже внутри BlocConsumer мы работаем с бизнес-логикой.
GetX:
AuthView. Аналогичный UI, но меньше кода.
Здесь всё тоже суперпросто. Мы получаем через Get.put зависимость authController и при нажатии кнопки вызываем метод. И всё готово.
Второй пример: List Data
BLoC:
HomeEvent. Обычный класс событий, ничем не отличается по структуре от
AuthEvent.
HomeBloc. Здесь стоит обратить внимание, что в Emit мы передаем класс состояния, а не просто вызываем State и меняем там переменную.
HomeState. Класс состояния. Отличается от AuthState тем, что содержит несколько классов — это DataFetched и DataFailure. У каждого из них есть Pagestate.
GetX:
HomeController. Здесь снова один класс, который использует GetXController. Но главное — он использует StateMixing. StateMixing похож на реализацию в BLoC, но имеет у себя под капотом уже замоканное состояние, которое, увы, мы не можем дополнить или поменять. Он хранит в себе RxStatus.success, RxStatus.error, Empty, Loading.
Еще в StateMixing мы передаем дженерик — то, что мы хотим видеть в нашем UI. Когда мы загружаем страницу, внутри бизнес-логики срабатывает OnInit. Мы вызываем состояние RxStatus.success и прописываем методы, как и с block.refreshData и addData. Дальше также через Try Catch вызываем либо RxStatus.success, либо RxStatus.error.
BLoC:
HomePage. Немного схоже с AuthPage, только здесь используем BlocBuilder.
Получаем зависимость тоже через BlocProvider. Здесь мы используем не BlocConsumer (потому что он нам не нужен), а только BlocBuilder. Но логика такая же, и события также вызываются Refresh-индикатором.
GetX:
HomeView. Чтобы получить зависимость, в HomeView мы используем Get.put. Для построения UI мы используем HomeController и вызываем в нем .obx. homeController.obx — это то же, что и BlocBuilder. Здесь мы не прописываем условия, а вызываем параметры.
Плюсы и минусы GetX и BLoC
Подведем итоги по каждому подходу и сравним их по четырем критериям:
Простота в использовании
У GetX более низкий порог входа, он проще в освоении, чем BLoC. Он предоставляет простой и понятный API для управления состоянием, маршрутизации и управления зависимостями.
Масштабируемость
BLoC дает более гибкий и модульный подход к управлению состоянием, и его можно масштабировать на больших и сложных приложениях. BLoC разделяет логику управления состоянием на отдельные блоки, которые легко тестировать. Это облегчает поддержку и обновление кода. Кроме того, в BLoC есть четкое разделение ответственности между слоями приложения, что облегчает масштабирование и улучшает производительность.
Тестирование
В BLoC есть мощные средства для тестирования потоков событий и состояний, например bloc_test.
Возможности
GetX предоставляет широкий спектр возможностей для маршрутизации и управления состоянием и зависимостями.
Что же выбрать при масштабировании Flutter-приложения?
Выбор между GetX и BLoC для масштабирования приложения зависит от конкретных требований проекта и ваших предпочтений. Если приложение относительно небольшое и простое, то GetX может быть более подходящим решением. Он предоставляет простой и понятный API для управления состоянием.
Однако, если у вас сложное приложение, то BLoC может подойти лучше. С ним вы получите более гибкий и модульный подход к управлению состоянием, а это помогает при масштабировании больших и сложных приложений.
P. S. Недавно я выступал с этой темой на Стачке 2024. Тогда аудиторию очень заинтересовал GetX, было много вопросов. При этом, из всего зала с GetX имели дело единицы.
Я буду рад ответить на ваши вопросы и здесь, в комментариях. Интересно узнать, что вы думаете о выборе между GetX и BLoC. Делитесь мыслями.
P. P. S. Мой коллега Саша Ворожищев ведет классный канал про Flutter — загляните как-нибудь.