0
0
FlutterHow-ToBeginner · 4 min read

How to Use BLoC in Flutter: Simple Guide with Example

To use BLoC in Flutter, create a Bloc class that manages state and events, then provide it to your widget tree using BlocProvider. Use BlocBuilder to rebuild UI based on state changes. This pattern separates business logic from UI for cleaner, testable code.
📐

Syntax

The basic syntax involves creating a Bloc class that extends Bloc<Event, State>. You define events and states as classes. Use BlocProvider to make the bloc available to widgets, and BlocBuilder to rebuild UI when state changes.

  • Bloc: Handles events and emits states.
  • Event: User actions or triggers.
  • State: UI representation at a moment.
  • BlocProvider: Injects bloc into widget tree.
  • BlocBuilder: Listens to bloc state and rebuilds UI.
dart
class CounterEvent {}
class IncrementEvent extends CounterEvent {}

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

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1)));
  }
}

// Usage in widget tree:
BlocProvider(
  create: (_) => CounterBloc(),
  child: BlocBuilder<CounterBloc, CounterState>(
    builder: (context, state) {
      return Text('Count: ${state.count}');
    },
  ),
)
💻

Example

This example shows a simple counter app using BLoC. Pressing the button sends an IncrementEvent to the bloc, which updates the state and rebuilds the UI with the new count.

dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

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

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1)));
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => CounterBloc(),
        child: const CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final bloc = context.read<CounterBloc>();
    return Scaffold(
      appBar: AppBar(title: const Text('BLoC Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text('Count: ${state.count}', style: const TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => bloc.add(IncrementEvent()),
        child: const Icon(Icons.add),
      ),
    );
  }
}
Output
A Flutter app with an AppBar titled 'BLoC Counter', a centered text showing 'Count: 0' initially, and a floating button with a plus icon. Pressing the button increments the count displayed.
⚠️

Common Pitfalls

  • Not disposing the bloc when no longer needed can cause memory leaks; use BlocProvider to handle lifecycle automatically.
  • Mutating state directly instead of emitting new state objects breaks immutability and causes UI not to update.
  • Forgetting to add events to the bloc means no state changes happen.
  • Using context.read<Bloc>() inside build() can cause issues; prefer BlocBuilder or BlocListener for reacting to state.
dart
/* Wrong: Mutating state directly */
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      // state.count += 1; // Wrong: state is immutable
      emit(CounterState(state.count + 1));
    });
  }
}

/* Right: Emit new state */
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) => emit(CounterState(state.count + 1)));
  }
}
📊

Quick Reference

  • Bloc: Business logic component handling events and states.
  • Event: Actions sent to bloc to trigger state changes.
  • State: Immutable data representing UI at a time.
  • BlocProvider: Injects bloc into widget tree and manages lifecycle.
  • BlocBuilder: Rebuilds UI when bloc state changes.
  • BlocListener: Reacts to state changes without rebuilding UI.

Key Takeaways

Use BlocProvider to inject and manage your bloc's lifecycle in the widget tree.
Define clear event and state classes to separate user actions from UI representation.
Emit new immutable states instead of mutating existing ones to update UI correctly.
Use BlocBuilder to rebuild UI based on bloc state changes.
Avoid accessing bloc directly inside build methods; prefer BlocBuilder or BlocListener.