Skip to content

Implementing BLoC

Implementing BLoC Patterns

The BLoC (Business Logic Component) pattern is a structured approach to managing state in Flutter apps by decoupling business logic from the UI. It uses streams to propagate state changes reactively, enabling predictable and testable code. At its core, BLoC relies on three components: events (user actions or external triggers), bloc (the logic that processes events), and states (the UI-relevant data emitted by the bloc). This pattern is particularly effective for complex apps requiring real-time updates or asynchronous operations.


Core BLoC Components

Events

Events represent user interactions or external triggers (e.g., button clicks, API calls). They are emitted as Stream<Event>, which the bloc subscribes to.
Example:

abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

Bloc

The bloc class contains the business logic to process events and emit states. It typically uses StreamController to manage event streams and state emissions.
Example:

class CounterBloc {
  final _counterController = StreamController<int>();
  Stream<int> get counterStream => _counterController.stream;

  void increment() {
    _counterController.add(_counterController.value + 1);
  }

  void decrement() {
    _counterController.add(_counterController.value - 1);
  }

  void close() => _counterController.close();
}

States

States are the data emitted by the bloc to update the UI. They are typically immutable objects that represent the current application state.
Example:

class CounterState {
  final int count;
  CounterState(this.count);
}


Implementing a BLoC Class

To implement a BLoC, define a class that processes events and emits states. Use StreamController to manage the flow of data.
Example: A simple counter app

class CounterBloc {
  final _eventController = StreamController<CounterEvent>();
  final _stateController = StreamController<CounterState>();

  Stream<CounterEvent> get eventStream => _eventController.stream;
  Stream<CounterState> get stateStream => _stateController.stream;

  void handleEvent(CounterEvent event) {
    if (event is IncrementEvent) {
      _stateController.add(CounterState(_stateController.value.count + 1));
    } else if (event is DecrementEvent) {
      _stateController.add(CounterState(_stateController.value.count - 1));
    }
  }

  void close() {
    _eventController.close();
    _stateController.close();
  }
}

Diagram:

[UI] --> [Event] --> [Bloc] --> [State] --> [UI]


Event-Driven State Transitions

BLoC relies on event streams to trigger state changes. Use StreamTransformer for complex logic, such as validating form inputs.
Example: Form validation

class LoginFormBloc {
  final _eventController = StreamController<LoginEvent>();
  final _stateController = StreamController<LoginState>();

  Stream<LoginEvent> get eventStream => _eventController.stream;
  Stream<LoginState> get stateStream => _stateController.stream;

  void handleEvent(LoginEvent event) {
    if (event is SubmitFormEvent) {
      final isValid = validateCredentials(event.username, event.password);
      _stateController.add(LoginState(isValid: isValid));
    }
  }

  bool validateCredentials(String username, String password) {
    // Business logic for validation
    return username.length > 3 && password.length > 5;
  }

  void close() {
    _eventController.close();
    _stateController.close();
  }
}


Best Practices for BLoC Implementation

  1. Single Responsibility: Each bloc should manage a single domain or feature.
  2. Avoid Tight Coupling: Use dependency injection to decouple blocs from specific UI components.
  3. Use StreamTransformer for Complex Logic: Handle data transformations or filtering in separate transformers.
  4. Close Streams Properly: Always call close() on StreamController to prevent memory leaks.
  5. Leverage flutter_bloc Package: Use the official package for simplified BLoC implementation (e.g., Bloc, BlocBuilder).

Key takeaways

  • BLoC separates business logic from UI, enabling reactive state management.
  • Events drive state transitions, with blocs processing logic and emitting states.
  • Use StreamController and StreamTransformer for managing data flow and transformations.
  • Follow best practices like single responsibility and proper stream cleanup for maintainable code.
  • Leverage the flutter_bloc package to simplify BLoC implementation in Flutter apps.