0% found this document useful (0 votes)
28 views9 pages

BLoC Pattern Overview

The BLoC (Business Logic Component) pattern in Flutter separates UI from business logic, using events and states to manage application flow. Events represent user interactions, while states reflect the app's current status, with the BLoC class handling the transformation of events into states using streams. The document outlines implementation steps, naming conventions, testing practices, and performance optimization strategies for maintaining a clean and efficient BLoC architecture.

Uploaded by

sanjib shrestha
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views9 pages

BLoC Pattern Overview

The BLoC (Business Logic Component) pattern in Flutter separates UI from business logic, using events and states to manage application flow. Events represent user interactions, while states reflect the app's current status, with the BLoC class handling the transformation of events into states using streams. The document outlines implementation steps, naming conventions, testing practices, and performance optimization strategies for maintaining a clean and efficient BLoC architecture.

Uploaded by

sanjib shrestha
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

BLoC Pattern Overview

The Business Logic Component (BLoC) pattern separates presentation (UI) from business logic in Flutter
apps. It treats the UI as a view that dispatches events and rebuilds on states, with the BLoC sitting in
between. The BLoC is a standalone class that uses Streams under the hood: you add events into the bloc,
and it emits new states via a state stream. This means your UI reacts only to changes in state, making the
code more modular and testable 1 2 .

• Events are inputs to the BLoC, typically user interactions (button taps, form submissions). By
convention they are named in the past tense (e.g. CounterIncrementPressed ) because they
represent actions that have occurred 3 .
• States are the outputs of the BLoC, representing the app’s state at a moment in time. States should
be nouns (e.g. CounterLoadSuccess ) and often use class subclasses or an enum to indicate
status 4 .
• Streams: Internally, a BLoC listens for added events and transforms them (often asynchronously)
into states which are emitted on its state stream. Flutter’s flutter_bloc package provides
BlocBuilder (like a StreamBuilder ) to rebuild UI when new states arrive 5 .

The flutter_bloc package wires BLoC classes into the widget tree. A BlocProvider (or
MultiBlocProvider ) injects a bloc into a widget subtree 6 , and widgets like BlocBuilder and
BlocListener rebuild or trigger callbacks on state changes. For example, BlocBuilder<MyBloc,
MyState>(builder: (context, state) { /* build UI based on state */ }) automatically
looks up the nearest provided MyBloc and rebuilds whenever its state changes 5 . ( BlocListener is
similar but used for side-effects like navigation or dialogs 7 .)

Implementing a BLoC (Step-by-Step)


To use the BLoC pattern, follow these steps for each feature:

1. Define Event classes: Create a Dart file (e.g. counter_event.dart ) with an abstract base and
subclasses for each event. For example:

// counter_event.dart
abstract class CounterEvent {} // base class
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}

These events represent user actions. Name them clearly (e.g. CounterIncrementPressed ),
following recommended conventions 3 .

1
2. Define State classes: Create a file (e.g. counter_state.dart ) with an abstract base and state
subclasses. Each state is a snapshot of the UI. For example:

// counter_state.dart
abstract class CounterState {} // base state
class CounterInitial extends CounterState {} // initial state
class CounterLoadSuccess extends CounterState {
final int count;
CounterLoadSuccess(this.count);
}

States should be nouns. In this example, CounterLoadSuccess holds the current counter value.
The initial state ( CounterInitial ) could represent count = 0 or loading. (You can also define
states like CounterLoadInProgress , CounterLoadFailure , etc. to reflect loading or error
status.)

3. Create the BLoC class: In a file (e.g. counter_bloc.dart ), extend Bloc<Event, State> from
flutter_bloc . Pass the initial state to super() , and use the on<Event> handlers to map
events to states. For example:

// counter_bloc.dart
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial()) {
// Handle increment event
on<CounterIncrementPressed>((event, emit) {
// Determine current count (from state or default 0)
final current = (state is CounterLoadSuccess)
? (state as CounterLoadSuccess).count
: 0;
// Emit a new state with incremented count
emit(CounterLoadSuccess(current + 1));
});

// Handle decrement event


on<CounterDecrementPressed>((event, emit) {
final current = (state is CounterLoadSuccess)
? (state as CounterLoadSuccess).count
: 0;
emit(CounterLoadSuccess(current - 1));
});
}
}

Here, CounterBloc listens for CounterIncrementPressed and CounterDecrementPressed


events. In each handler, it retrieves the current count from the previous state (using a type check)

2
and then calls emit(...) with a new state. This causes the counterBloc.state stream to emit
the new state.

(Tip: All event handlers can be async, so you can await calls and then emit after an API or database call.)

1. Provide the BLoC to the UI: Wrap your widget tree with BlocProvider so that child widgets can
access the bloc. For example, in main.dart :

void main() {
runApp(
BlocProvider(
create: (_) => CounterBloc(), // create and provide CounterBloc
child: MyApp(),
),
);
}

Inside your app, use BlocBuilder<CounterBloc, CounterState> (or BlocListener ) to


react to state changes. For example:

class CounterPage extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
final count = (state is CounterLoadSuccess) ? state.count : 0;
return Text('$count', style:
Theme.of(context).textTheme.headline4);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'inc',
child: Icon(Icons.add),
onPressed: () => context.read<CounterBloc>()
.add(CounterIncrementPressed()),
),
SizedBox(height: 8),
FloatingActionButton(
heroTag: 'dec',

3
child: Icon(Icons.remove),
onPressed: () => context.read<CounterBloc>()
.add(CounterDecrementPressed()),
),
],
),
);
}
}

In the above, BlocBuilder rebuilds its child widget ( Text ) whenever the bloc’s state changes
5 . The context.read<CounterBloc>().add(...) call dispatches an event to the bloc.

Example: Authentication Flow

As a more realistic example, consider an authentication flow. You might define events like “app started”,
“user logged in”, “user logged out”, and states like “authenticated” or “unauthenticated”:

// auth_event.dart
abstract class AuthEvent {}
class AuthStarted extends AuthEvent {}
class AuthLoggedIn extends AuthEvent {
final User user;
AuthLoggedIn(this.user);
}
class AuthLoggedOut extends AuthEvent {}

// auth_state.dart
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
AuthAuthenticated(this.user);
}
class AuthUnauthenticated extends AuthState {}

// auth_bloc.dart
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc() : super(AuthInitial()) {
on<AuthStarted>((event, emit) async {
emit(AuthLoading());
// e.g. check token or session
try {
final user = await AuthService.getCurrentUser();
if (user != null) {

4
emit(AuthAuthenticated(user));
} else {
emit(AuthUnauthenticated());
}
} catch (_) {
emit(AuthUnauthenticated());
}
});
on<AuthLoggedIn>((event, emit) {
emit(AuthAuthenticated(event.user));
});
on<AuthLoggedOut>((event, emit) {
emit(AuthUnauthenticated());
});
}
}

• On App Start ( AuthStarted ), the bloc might check a repository or secure storage for a logged-in
user. It first emits AuthLoading , then either AuthAuthenticated(user) or
AuthUnauthenticated() depending on the result.
• When the user successfully logs in ( AuthLoggedIn event), the bloc emits AuthAuthenticated .
• On logout ( AuthLoggedOut ), the bloc emits AuthUnauthenticated .

UI widgets would use BlocListener to navigate (e.g. show login screen if unauthenticated) and
BlocBuilder / BlocConsumer to rebuild parts of the UI based on auth state.

Example: API Integration (Weather Fetch)

For an API-driven feature, define events for “fetch data” and states for loading, success, or failure. For
example, a weather feature:

// weather_event.dart
abstract class WeatherEvent {}
class FetchWeather extends WeatherEvent {
final String city;
FetchWeather(this.city);
}

// weather_state.dart
abstract class WeatherState {}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
final WeatherData weather;
WeatherLoadSuccess(this.weather);
}

5
class WeatherLoadFailure extends WeatherState {}

// weather_bloc.dart
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
final WeatherRepository repository;
WeatherBloc(this.repository) : super(WeatherInitial()) {
on<FetchWeather>((event, emit) async {
emit(WeatherLoadInProgress());
try {
final weather = await repository.fetchWeather(event.city);
emit(WeatherLoadSuccess(weather));
} catch (_) {
emit(WeatherLoadFailure());
}
});
}
}

Here, when FetchWeather is added, the bloc emits WeatherLoadInProgress , calls the repository,
then emits WeatherLoadSuccess with the data or WeatherLoadFailure on error (see example
pattern 8 ). In the UI, you’d dispatch FetchWeather(city) (e.g. on a form submit) and use
BlocBuilder<WeatherBloc, WeatherState> to show a loading indicator, the weather data, or an error
message based on the current state.

Folder Structure & Naming Conventions


For larger apps, a clean project structure and consistent naming helps maintainability. Common practices
include feature-based organization and following Dart style. For example:

• Folders by feature or layer. You might have:

lib/
features/ // (or 'blocs'/'presentation' as top-level)
counter/
bloc/
counter_bloc.dart
counter_event.dart
counter_state.dart
data/
counter_repository.dart
ui/
counter_page.dart
auth/
bloc/
auth_bloc.dart

6
auth_event.dart
auth_state.dart
data/
auth_repository.dart
ui/
login_page.dart
main.dart

Or, as another pattern:

lib/
blocs/ // all blocs grouped
counter_bloc/
counter_bloc.dart
counter_event.dart
counter_state.dart
auth_bloc/
auth_bloc.dart
...
data/ // data layer (models, repositories)
presentation/ // UI layer (widgets/screens)
main.dart

In either case, keep related files together and use subdirectories for clarity.

• File/class naming. Use lowercase_with_underscores.dart for file names 9 (e.g.


counter_bloc.dart , login_page.dart ). Name classes in UpperCamelCase . By convention,
suffix files to indicate contents: e.g. counter_event.dart , counter_state.dart ,
counter_bloc.dart .

• Events and States naming. Follow Bloc conventions 3 4 . For example:

• Base classes: CounterEvent and CounterState .


• Events in past tense: CounterStarted , CounterIncrementPressed .

• States as nouns: CounterInitial , CounterLoadSuccess , AuthAuthenticated , etc.


This consistency helps other devs quickly recognize patterns.

• Equatable for models. Use the Equatable package on state or event classes (and data models) to
simplify value equality. For example:

class User extends Equatable {


final String id;
User(this.id);
@override

7
List<Object?> get props => [id];
}

This ensures that identical states aren’t considered different simply by object reference (important
for avoiding unnecessary rebuilds) 10 .

Testing, Performance, and Clean Code


Testing: Because Blocs are pure Dart classes, they are easy to unit-test in isolation 11 . Use the
bloc_test package to assert state sequences. For example, a counter test might look like:

blocTest<CounterBloc, CounterState>(
'emits [CounterLoadSuccess(1)] when CounterIncrementPressed is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(CounterIncrementPressed()),
expect: () => [CounterLoadSuccess(1)],
);

This verifies that after an increment event, the bloc emits a state with count 1 (similar to documentation
example 12 ). Write tests for each event/state transition, mocking any external dependencies (e.g.
repositories) so tests run predictably. Group related tests and use descriptive names. Testing BLoCs helps
ensure your business logic is correct and remains so during refactoring.

Clean Code Practices: - Single Responsibility: Each Bloc class should handle one feature’s logic. Keep UI
logic out of the BLoC; it should only process events and emit states. - Immutability: Make state classes
immutable (use final fields). If a state has many fields, you can provide a copyWith method to create
modified copies rather than mutating state. - Equatable: As noted, extending Equatable on states and
events makes comparisons easy and prevents emitting duplicate states (which avoids unnecessary rebuilds)
10 . - Minimize Logic in UI: UI widgets should trigger events but not compute business logic. BLoCs

encapsulate logic (e.g. sorting lists, performing calculations, handling retries, etc.), keeping widgets simpler.

Performance Optimization: - Avoid Excessive State Emissions: Don’t emit a new state for every tiny
change. Instead, batch related updates. For example, if multiple properties change together, emit one state
with all updates 13 . This reduces rebuild thrash. - Throttle/Debounce Rapid Events: For events that fire
quickly (e.g. on text input or scrolling), use debouncing/throttling. The flutter_bloc package supports
transforming events (e.g. via transformEvents ) or you can use RxDart. This ensures the bloc processes
only as often as needed 14 . - Selective UI Updates: Use widgets like BlocSelector to rebuild only when
a specific slice of state changes. For example, if a state has many fields, you can select just one field for a
particular widget. This prevents unnecessary rebuilds of the whole subtree 15 . - Resource Cleanup: When
using BlocProvider , closing the bloc is automatic if the bloc was created inside it 6 . If you manually
create blocs or streams, be sure to call .close() in dispose() to free resources. - Efficient Streams:
Only listen/subscribe where needed. Avoid multiple identical BlocBuilder s listening to the same bloc if
one would suffice. Also, ensure async calls (like API requests) use await so you emit states in order.

8
By following these practices—proper testing, clear naming/structure, and mindful performance strategies—
your BLoC-based Flutter app will remain maintainable, efficient, and robust. Remember that the strength of
the BLoC pattern is in its clarity: events go in, states come out, and everything in between is testable logic
2 1 .

Sources: Concepts and examples above are based on the official Bloc documentation and community best
practices 2 1 3 4 5 6 12 .

1 How to Manage State in Flutter with BLoC Pattern? | GeeksforGeeks


https://www.geeksforgeeks.org/how-to-manage-state-in-flutter-with-bloc-pattern/

2 Flutter BLoC Tutorial: Learning State Management in Flutter


https://www.dhiwise.com/post/flutter-bloc-tutorial-understanding-state-management

3 4 Naming Conventions | Bloc


https://bloclibrary.dev/naming-conventions/

5 6 7 Flutter Bloc Concepts | Bloc


https://bloclibrary.dev/flutter-bloc-concepts/

8 Mastering Flutter App Architecture with Bloc | Gurzu


https://gurzu.com/blog/mastering-flutter-app-architecture-with-bloc/

9 Effective Dart: Style | Dart


https://dart.dev/effective-dart/style

10 Flutter Login | Bloc


https://bloclibrary.dev/tutorials/flutter-login/

11 12 Testing | Bloc
https://bloclibrary.dev/testing/

13 14 15 Optimizing Performance in Flutter Using BLoC


https://tillitsdone.com/blogs/flutter-bloc-performance-guide/

You might also like