BLoC file structure
Published: 10 January 2024
Tagged:
BLoC

What we strive for is readable (assuming a consistent architectural style), testable code that is easy to extend and maintain.


In applications using the BLoC (Business Logic Component) architecture, there is a clear separation between business logic and the user interface.

It’s important to strictly separate these layers: the UI should be independent of business logic and display data based on the state provided by it.

Business logic forms a simplified description of how the UI should look at any given moment. This separation improves code cleanliness, testability, readability, and ease of maintenance.



Structure

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/

This directory contains the BLoC components. The key rule is that all data manipulation must happen at this level.


Working with services, preparing data, and forming states for the user interface. If you notice that some parts of the code are getting bulky, extract them into separate services, helpers, or providers and pass them as dependencies to the bloc.


  1. feature_event.dart — for events, follow this rule: if you have an atomic action, create a corresponding event. Avoid situations where you have two actions such as delete/add and you create a single Event with an action parameter. It’s preferable to create two separate Event classes — one for deletion and one for creation. This improves readability and clarity, and ensures atomic events.

  2. feature_state.dart — contains a set of states. It’s important not to add extensions (extensions) to this class for computing additional properties. If such computations are needed, add a new property and perform the computation in feature_bloc, then place the result in feature_state.


models/

This directory holds data models such as keys.dart, which represents keys for UI components, as well as any models for rendering widgets related to this screen.



cubit/

This directory contains all cubits, which are mostly responsible for direct interaction with the View. For example, navigation_cubit.dart contains calls responsible for screen-opening actions. We can call an openScreen method in bloc and handle the related state in the View. It’s also advisable to create separate cubits if they differ in functionality or purpose.



route/

This directory contains components responsible for the component’s navigation.


  1. External_router provides a convenient interface for displaying the current screen. (Not needed if the component is not a screen (Route) but a simple widget.)

  2. Internal_router provides an interface for opening other widgets and screens.

  3. feature_route (view_provider) — this is where all dependencies are configured and all components are initialized. All dependencies that will be accessed at the UI level using the Provider framework are registered here.

    Use BlockProvider for the bloc, and RepositoryProvider for all other services. Also create a feature_arguments class if more than one parameter needs to be passed.


view/

Contains the screen widget. In this case, feature_view.dart represents the main screen. Add all necessary event handlers related to the screen here via BlocListener (e.g., navigation handling, processing NavigationCubit or PaginationCubit states).



widgets/

This directory holds reusable widgets used on the screen. For example, tile_item.dart can be a widget for rendering an individual list item.


More examples:

  • feature_loading.dart — add a shimmer for the screen
  • feature_loaded.dart — main view where the UI with data is rendered
  • feature_empty.dart / feature_error.dart — error and empty state respectively

Very important

Note that no additional computations for displaying data should happen in the View. The only thing permitted in the View is simple conditions based on boolean values stored in state. This keeps the UI code clean and readable, and reduces the complexity of visual components.



Questions? Write here – @alobanov