BLoC
01 / 02

BLoC: Widget Integration, Testing & Best Practices

BLoC: Widget Integration, Testing & Best Practices

Providing & Consuming a BLoC

// Provides a LoginBloc instance to the widget subtree below --
// no manual threading through every constructor needed
BlocProvider(
  create: (context) => LoginBloc(authService),
  child: LoginScreen(),
)

// BlocBuilder rebuilds when a new state is emitted
BlocBuilder<LoginBloc, LoginState>(
  builder: (context, state) {
    if (state is LoginLoading) return CircularProgressIndicator();
    if (state is LoginSuccess) return Text('Welcome, ${state.user.name}');
    return LoginForm();
  },
)

// BlocListener runs a side effect (snackbar, navigation) WITHOUT
// rebuilding any UI itself
BlocListener<LoginBloc, LoginState>(
  listener: (context, state) {
    if (state is LoginFailure) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.message)));
    }
  },
  child: LoginForm(),
)

Dispatching Events: read() vs. watch()

// context.read<T>() -- gets the bloc WITHOUT subscribing to changes,
// appropriate inside a one-time callback like onPressed
ElevatedButton(
  onPressed: () => context.read<LoginBloc>().add(LoginSubmitted(email, password)),
  child: Text('Log in'),
)

// context.watch<T>() -- subscribes to state changes, triggering a
// rebuild whenever a new state is emitted (used inside build())

Testing with bloc_test

// Tests the exact state sequence emitted -- no widget rendering needed
blocTest<LoginBloc, LoginState>(
  'emits [LoginLoading, LoginSuccess] on successful login',
  build: () => LoginBloc(authService),
  act: (bloc) => bloc.add(LoginSubmitted('a@b.com', 'pass')),
  expect: () => [LoginLoading(), LoginSuccess(testUser)],
);

Value Equality with Equatable

// BlocBuilder compares previous/new state to decide if a rebuild
// is needed -- without value equality, identical-content states with
// different object instances would cause unnecessary rebuilds
class LoginSuccess extends LoginState with EquatableMixin {
  final User user;
  LoginSuccess(this.user);

  @override
  List<Object> get props => [user];
}

Choosing setState vs. BLoC

A simple single-screen widget might reasonably use setState() with no real downside. BLoC's structured separation pays off as an app grows: sharing state across distant screens, and testing business logic independently of any widget rendering.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free