Skip to content

State Management (BLoC)

The mobile client uses the BLoC (Business Logic Component) pattern for state management. This pattern helps to separate the presentation layer from the business logic, making the application more structured, testable, and maintainable.

While most features have their own dedicated BLoC, the most important one is the global AppBloc, which manages the application’s core state, including user authentication, settings, and the overall application status (AppStatus).

Every feature in the application that requires managing state has its own BLoC.

A BLoC is made up of three main parts:

  1. Events: Events are the inputs to a BLoC. They are dispatched from the UI in response to user interactions (e.g., a button press) or lifecycle events (e.g., the page loading). Events are defined as simple classes that extend a base Event class.

  2. States: States are the outputs of a BLoC. They represent a part of the application’s state and are emitted by the BLoC in response to events. The UI listens to the stream of states and rebuilds itself to reflect the latest state. States are typically modeled as immutable classes.

  3. BLoC: The BLoC itself is the component that sits between the UI and the data layer. It receives events, processes them (often by interacting with a repository), and emits new states.

Let’s look at the AccountBloc, which manages the state for the user’s account screen.

  • Event: When a user toggles following a topic, the UI dispatches an AccountFollowTopicToggled event, which contains the Topic object.

    lib/account/bloc/account_event.dart
    class AccountFollowTopicToggled extends AccountEvent {
    const AccountFollowTopicToggled({required this.topic});
    final Topic topic;
    }
  • BLoC Logic: The AccountBloc listens for this event. In its event handler, it updates the user’s preferences by calling the _userContentPreferencesRepository and then emits a new state.

    lib/account/bloc/account_bloc.dart
    on<AccountFollowTopicToggled>(_onAccountFollowTopicToggled);
    Future<void> _onAccountFollowTopicToggled(
    AccountFollowTopicToggled event,
    Emitter<AccountState> emit,
    ) async {
    // ... logic to add/remove topic from preferences ...
    final updatedPrefs = // ...
    // Persist the change
    await _userContentPreferencesRepository.update(item: updatedPrefs);
    // Emit the new state
    emit(state.copyWith(status: AccountStatus.success, preferences: updatedPrefs));
    }
  • State: The AccountState holds the current user’s UserContentPreferences. When the BLoC emits a new state with the updated preferences, the UI rebuilds.

    lib/account/bloc/account_state.dart
    class AccountState extends Equatable {
    const AccountState({
    this.status = AccountStatus.initial,
    this.preferences,
    // ...
    });
    final AccountStatus status;
    final UserContentPreferences? preferences;
    // ...
    }
  • UI Integration: The FollowedTopicsListPage uses a BlocBuilder to listen to the AccountBloc. When the state changes, it rebuilds the list of topics, and the UI reflects whether the topic is followed or not.

    lib/account/view/manage_followed_items/topics/followed_topics_list_page.dart
    // ...
    body: BlocBuilder<AccountBloc, AccountState>(
    builder: (context, state) {
    // ... build UI based on state.preferences.followedTopics ...
    },
    ),
    // ...

This unidirectional data flow—from UI event to BLoC to new UI state—is the foundation of state management in the application.