Всем привет! Я Айдар Мавлетбаев, 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) тоже разделяет приложение на три слоя:

  1. Модель (Model) — это компонент, который отвечает за хранение и обработку данных приложения. Модель представляет собой набор классов, которые описывают структуру данных и предоставляют методы для работы с ними.

  2. Вид (View) — отвечает за отображение данных на экране. Вид представляет собой набор виджетов, которые отображают данные, полученные из модели.

  3. Контроллер (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 — загляните как-нибудь.

Что еще почитать

Комментарии (11)


  1. Mitai
    21.05.2024 16:50
    +2

    нет тут ни какого выбора если приложение маленькое хватит и инхеритов гетИкс кусок говнища это давно всем известно


    1. Flex_Aidar Автор
      21.05.2024 16:50

      Можете конструктивно описать свою позицию?


      1. lil_master
        21.05.2024 16:50
        +3

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

        Но я не считаю GetX говном, во-первых года два прошло, он наверняка улучшился, во-вторых ту простоту и скорость, которую он даёт хочется боготворить.

        Согласен что BLoC - это новое хорошо забытое старое MVC. Но слишком много классов.

        Моё мнение: паттерн не важен. Если опытный человек ясно видит что он делает - любой паттерн будет и удобочитаемым и производительным и т д. На старте невозможно учесть всё, да и не нужно, все популярные проекты сталкивались с переписыванием своего кода с нуля пару тройку раз, потому что изначально не было учтено расширение куда-то там, но если всё учитывать - это тоже снижает скорость разработки так, что один день проект просто может стать неактуальным, потому что какой то на коленке сделанный конкурент уже выстрелил. Часто в современных проектах то что можно реализовать одним виджетом растягивают в класс, это печально.

        Bloc хорош для тех приложений, когда проект переписывают с Java или Csharp на Dart. GetX хорош для быстрого создания прототипа. А огромное тестирование конечно ждёт обоих)


      1. gangsterpp
        21.05.2024 16:50

        Кто будет текст контроллеры освобождать ?

        Только по этим двум строкам понятен непрофессионализм и незнание фреймворка


  1. poimtsev
    21.05.2024 16:50
    +2

    А что скажете про MobX?


    1. serg_dominator
      21.05.2024 16:50
      +1

      Хорошая и недооцененная штука. Я по ней делал доклад год назад https://www.youtube.com/watch?v=hIVirQHx1nk (надеюсь, тут можно указывать ссылку).

      Автор тот же, который делал MobX для React (это порт с веба), есть коммьюнити с несколькими активными чуваками и спонсорами, что позволяет предположить, что он не загнется в ближайшее время. Бойлерплейта значительно меньше, чем в Bloc.

      Два минуса я бы все-таки упомянул:

      1. В документации модели делают как сторы - не согласен с этим. Модели надо делать как обычные классы с final переменными, а обсерваблы как final Observable<T>.

      2. Не всегда очевидно, на какую именно переменную триггерится Observer. Он реагирует на любые обсерваблы внутри себя. Сейчас в issues идет обсуждение, не сделать ли дополнительно обсерверы с ручным (явным) подписанием на обсерваблы (как Selector в провайдере).


  1. serg_dominator
    21.05.2024 16:50
    +2

    Опишем конструктивно. Использовать GetX - очень плохая идея. В нем проблема именно в том, что он является одновременно всем: и стейт-менеджер, и сервис-локатор, и роутер, и локализации, и разные утилитки - и всё это работает из рук вон плохо, с огромным количеством багов.
    Очень много функционала, копирующего встроенный функционал Флаттера (напр., update - это ChangeNotifier, показ диалогов). Весь код краденный или плохо переписанный с других пакетов (без указания авторства). obs-переменные - списаны с пакета observabl_ish. Локализация основана на устаревшем easy_localization, и так далее.
    Автор забил на своё творение и уже года 2 его не поддерживает.
    Я поначалу интересовался им (хотя и не рискнул тянуть в прод), но когда вышел Navigator 2.0, а GetX его спустя полгода так и не поддержал, понял, что можно забыть про эту игрушку.

    По своей сути, это усовершенствованный Server Locator 2.0, который дает возможность не использовать Context.

    А что, простите, плохого, в Context? Это очень важная концепция во Флаттере, без которой не обойтись. Если вы не понимаете его, рекомендую разобраться. Насколько я помню, в GetX из-за отсутствия контекста были баги с неправильной отрисовкой диалогов.

    Горячо рекомендую взглянуть на MobX - тот же принцип с реактивным точечным обновлением UI, но активно мэйнтейнится, и всё работает идеально. Я туда контрибьютил немного. GetX в сравнении с ним - отсутствует необходимая оптимизация (компьютеды и экшены).
    Мы на своем проекте используем связку MobX + get_it + go_router + slang - в итоге имеем тот же тулсет, что и GetX, но все пакеты регулярно обновляются и поддерживаются.


  1. Krushiler
    21.05.2024 16:50

    Интересный факт: GetX - единственный очень популярный пакет без отметки flutter favourite. С чего бы это...


  1. undersunich
    21.05.2024 16:50

    У GetX нормальная документация - все понятно там. А Вы пишете за это минус для него - тут как раз плюс. Вы говорите что масштабируемость лучше у Блок-а. Масштабируемость это как раз увеличение сущностей(ибо это увеличение обьемов задач) А Блок оперирует минимальными сущностями. При больших обьемах это "ад многообразия"* 3 ( ибо у блока 3 файла на сущность) И после этого Вы пишете что это его плюс? Используя Блок Вы тут еще и get_it используете. Читать такой код(еще и смасштабированый) может только автор и то когда напьется. Блок слишком многословен и на нем по практике настаивают в основном люди-хейтеры. Так что статья в принципе хорошая но точно ее нельзя считать руководством к действию


  1. PackRuble
    21.05.2024 16:50

    Статья неконструктивна. Вы не можете сравнивать несопоставимые вещи. get - это огромный комбайн всего и вся сомнительного качества, пакет bloc предоставляет только стейт-менеджмент.

    Далее, сквозь весь текст путаются понятия. Если коротко, BLoC != пакету bloc, MVC != GetX, BLoC != GetX .

    Ещё кое-что. Признайтесь честно, вы смотрели исходный код get ? Подключите последнюю preview версию get: ^5.0.0-release-candidate-5 , которая есть на pub.dev и посмотрите. Посмотрите, что эта библиотека из себя представляет, что импортирует, из чего состоит, в особенности проверьте степень документации и комментирования, и обратите внимание на закомментированные участки кода. Вот, из прекрасного:

    Оно вам точно нужно в проде или где-то ещё?
    путь get-5.0.0-release-candidate-5/lib/get_navigation/src/routes/get_transition_mixin.dart
    путь get-5.0.0-release-candidate-5/lib/get_navigation/src/routes/get_transition_mixin.dart
    ниже в этом же файле
    ниже в этом же файле

    Не стройте иллюзий, этим нельзя пользоваться всерьёз.


  1. Pardych
    21.05.2024 16:50
    +1

    Clean Architecture это принципы построения архтектуры, не архитектура и не паттерн.

    DDD это так же набор принципов.

    MVC это таки паттерн. Для слоя презентации, ни разу не архитектура, славится довольно сильной для последней моды связностью, предоставляя вьюшке самой обращаться за данными в модель и интерпретировать их, хотя двунаправленно общается с обеими, если это убрать будет уже MVP.

    BLoC это брендированная реализация MVI с упором на "некий компонент бизнес-логики" в аббревиатуре не фигурирующий. Но вы же не понимаете паттерны буквально? Если у нас три буквы - это не означает что в реализации будет только три класса. Он у нас в примитивном виде и стейт-менеджер и интерактор, причем в одноименном пакете есть вместо Bloc-а Cubit который тупо вью модель из MVVM (так же легким движением руки превращающееся в MVI), так что можно вот этими самыми лапками из него сделать такой же блок. Или свалить эту обязанность на настоящий интерактор, который применим и в блоке. Короче так похожи, что мама родная не узнает.

    GetX - в первую голову сервис-локатор и потом все остальное, стейт менеджмент там прямо скажем так себе, локаль требует доработки напильником, чо там еще есть? У меня есть в проде поделка с гетиксом, он там только для di и локали, причем последнюю приходится генерить самопальным скриптом иначе это оч плохо бьется с командой когда у тебя есть, например, чувак отвечающий за локализацию. С тем же успехом можно было бы взять гет ит как сервис-локатор и нагенерить удобную для себя локализацию самому (+1 не сильно-то и сложный класс). На то и рассчитывал если что-то пойдет не так. Но вроде покашляет еще.

    Дальше больше. Как будто надергали кусков ото всюду даже не попытавшись их осмыслить. Немного страшно за флаттер-комьюнити с такими спикерами и таким залом.