BLoC file structure (Part 1)
Published: 10 January 2024
Tagged:
BLoC

К чему мы стремимся - это создать читаемый (с предположением использования единого архитектурного стиля), тестируемый код, который легко расширяется и поддерживается.


В приложениях, использующих архитектуру BLoC (Business Logic Component), обеспечивается четкое разделение между бизнес-логикой и пользовательским интерфейсом.

Важно строго разграничивать эти слои: интерфейс должен быть независимым от бизнес-логики и отображать данные на основе состояния, предоставленного ею.

Бизнес-логика формирует упрощенное описание того, как интерфейс должен выглядеть в данный момент. Такое разделение повышает чистоту кода, его тестируемость, читаемость и упрощает сопровождение.



Структура

feature/
|-- bloc/
|   |-- feature_bloc.dart
|   |-- feature_event.dart
|   |-- feature_state.dart
|
|-- models/
|   |-- feature_keys.dart
|
|-- cubit/
|   |-- navigation_cubit.dart
|
|-- route/
|   |-- feature_internal_router.dart
|   |-- feature_external_router.dart
|   |-- feature_route.dart (feature_view_provider.dart)
|
|-- view/
|   |-- feature_view.dart
|
|-- widgets/
|   |-- feature_empty.dart
|   |-- feature_loading.dart
|   |-- feature_error.dart
|   |-- feature_loaded.dart


bloc/

Этот каталог содержит BLoC – компоненты. Основное правило заключается в том, что все манипуляции с данными должны осуществляться на этом уровне


Pабота с сервисами, подготовка данных и формирование состояний для пользовательского интерфейса. Если вы замечаете, что некоторые части кода начинают выглядеть громоздко, то их следует вынести в отдельные сервисы, хелперы или провайдеры и передать в качестве зависимости в bloc.


  1. feature_event.dart для событий мы должны придерживаться следующего правила: если у вас есть атомарное действие, создается соответствующее событие. Следует избегать ситуаций, когда у вас есть два действия, такие как удалить/добавить, и вы создаете один Event с параметром действия. В таком случае более предпочтительным подходом является создание двух отдельных Event - одного для удаления и другого для создания. Это повышает читаемость и ясность кода, а также обеспечивает атомарность событий.

  2. feature_state.dart - содержит набор состояний. Важно никоим образом не добавлять расширения (extensions) к этому классу для вычисления дополнительных свойств. Если возникает необходимость в таких вычислениях, лучше добавить новое свойство и произвести вычисления в классе feature_bloc, а затем поместить полученное значение в feature_state.


models/

В этом каталоге размещаются модели данных, такие как keys.dart, которая представляет ключи для компонентов пользовательского интерфейса, а также любые модели для отрисовки виджетов, связанных с данным экраном.



cubit/

В этом каталоге находятся все кубиты, которые в большинстве своем отвечают за прямое взаимодействие с View. Например, navigation_cubit.dart содержит вызовы, отвечающие за действия открытия экранов. Мы можем вызвать метод openScreen в bloc и обработать состояние, связанное с открытием этого экрана во View. Также желательно создавать отдельные кубиты, если они отличаются по своей функциональности или смыслу.



route/

Здесь хранятся компоненты отвечающие за навигацию компонента.


  1. External_router предоставляет удобный интефейс показа текущего экрана (Не нужен если в качестве компонента используется не экран (Route), а простой виджет.

  2. Internal_router предоставляет интерфейс открытия других виджетов и экранов.

  3. feature_route (view_provider) - В этом месте происходит конфигурация всех зависимостей и инициализация всех компонентов. Здесь регистрируются все зависимости, к которым будет осуществляться доступ на уровне пользовательского интерфейса с использованием фреймворка Provider.

    Для блока следует использовать BlockProvider, а для всех остальных сервисов - RepositoryProvider. Важно также создавать класс feature_arguments, если необходимо передать более одного параметра.


view/

Cодержит виджет экрана. В данном случае, feature_view.dart представляет собой главный экран. Также здесь нужно добавить все необходимые обработчики событий которые относятся к экрану через BlocListener (например то что касается навигации, обработка состояний NavigationCubit или PaginationCubit).



widgets/

В этом каталоге можно хранить многоразовые виджеты, которые используются на экране. Например, tile_item.dart может быть виджетом для отображения отдельного элемента в списке.


Еще примеры:

  • feature_loading.dart - добавляем шиммер для экрана
  • feature_loaded.dart - основное view на котором отрисовываем UI с данными
  • feature_empty.dart/feature_error.dart - ошибка и пустое состояние соответственно

Очень важно

Добавлю, что в View не должно происходить никаких дополнительных вычислений для отображения данных. Единственное, что разрешается во View, это простейшие условия, зависящие от логических значений, хранящихся в state. Это позволяет поддерживать чистоту и читаемость кода в пользовательском интерфейсе и уменьшает сложность визуальных компонентов.



Есть вопросы? Пишите сюда – @alobanov