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.
-
feature_event.dart для событий мы должны придерживаться следующего правила: если у вас есть атомарное действие, создается соответствующее событие. Следует избегать ситуаций, когда у вас есть два действия, такие как удалить/добавить, и вы создаете один
Event
с параметром действия. В таком случае более предпочтительным подходом является создание двух отдельных Event - одного для удаления и другого для создания. Это повышает читаемость и ясность кода, а также обеспечивает атомарность событий. -
feature_state.dart - содержит набор состояний. Важно никоим образом не добавлять расширения (extensions) к этому классу для вычисления дополнительных свойств. Если возникает необходимость в таких вычислениях, лучше добавить новое свойство и произвести вычисления в классе feature_bloc, а затем поместить полученное значение в feature_state.
models/
В этом каталоге размещаются модели данных, такие как keys.dart, которая представляет ключи для компонентов пользовательского интерфейса, а также любые модели для отрисовки виджетов, связанных с данным экраном.
cubit/
В этом каталоге находятся все кубиты, которые в большинстве своем отвечают за прямое взаимодействие с View. Например, navigation_cubit.dart содержит вызовы, отвечающие за действия открытия экранов. Мы можем вызвать метод openScreen в bloc и обработать состояние, связанное с открытием этого экрана во View. Также желательно создавать отдельные кубиты, если они отличаются по своей функциональности или смыслу.
route/
Здесь хранятся компоненты отвечающие за навигацию компонента.
-
External_router предоставляет удобный интефейс показа текущего экрана (Не нужен если в качестве компонента используется не экран (Route), а простой виджет.
-
Internal_router предоставляет интерфейс открытия других виджетов и экранов.
-
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. Это позволяет поддерживать чистоту и читаемость кода в пользовательском интерфейсе и уменьшает сложность визуальных компонентов.