BLoC — The Magic of Single State Class

Anugrah Dwi Kustanto
3 min readJun 22, 2024

--

In the world of mobile app development, especially when using Flutter, one architectural pattern that stands out for its simplicity and efficiency is BLoC (Business Logic Component). BLoC helps in managing the state of an application in a clean, reusable, and testable manner. One of the magical aspects of BLoC is its ability to handle state using a single state class. Let’s dive into how this works and why it’s so powerful.

What is BLoC?

BLoC is an architectural pattern introduced by Google that aims to separate business logic from the UI. By doing so, it promotes a clear distinction between the presentation layer and the business logic, making your code more modular and easier to maintain. In BLoC, data flows in a unidirectional way, from the UI to the business logic and back to the UI.

The Concept of Single State Class

In traditional state management, each state change might be represented by a different class. This can lead to a proliferation of state classes, making the codebase harder to manage. The single state class approach, on the other hand, encapsulates all possible states in one class. This simplification makes it easier to manage and understand state transitions.

Benefits of Single State Class

  1. Simplicity: With a single state class, you don’t have to juggle multiple state classes. This reduces complexity and makes your code more readable.
  2. Ease of Maintenance: With fewer classes to manage, your code becomes easier to maintain and refactor.
  3. Clarity: A single state class provides a clear and unified view of all possible states and their transitions, improving the overall understanding of the app’s state management.

Implementing Single State Class in BLoC

Let’s see how we can implement a single state class in a BLoC pattern with a simple example. Consider a counter app where the state can either be the initial state or an updated count.

Step 1: Define the State Class

class CounterState {
final int count;

CounterState({required this.count});

factory CounterState.initial() {
return CounterState(count: 0);
}

CounterState copyWith({int? count}) {
return CounterState(
count: count ?? this.count,
);
}
}

Here, CounterState represents the state of the counter. It has a single property, count. The initial factory constructor sets the initial count to zero. The copyWith method allows for creating a new state with updated properties while keeping others unchanged.

Step 2: Define the Events

abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}

Events are actions that can trigger state changes. In this case, we have two events: Increment and Decrement.

Step 3: Create the BLoC

class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState.initial());

@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state.copyWith(count: state.count + 1);
} else if (event is Decrement) {
yield state.copyWith(count: state.count - 1);
}
}
}

The CounterBloc extends the Bloc class from the flutter_bloc package. It takes CounterEvent as events and CounterState as the state. The mapEventToState method maps incoming events to new states using the copyWith method.

Step 4: Integrate BLoC with UI

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (context) => CounterBloc(),
child: CounterScreen(),
),
);
}
}

class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () => BlocProvider.of<CounterBloc>(context).add(Increment()),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () => BlocProvider.of<CounterBloc>(context).add(Decrement()),
child: Icon(Icons.remove),
),
],
),
);
}
}

In the UI layer, BlocProvider and BlocBuilder are used to provide and build the BLoC, respectively. The floating action buttons dispatch Increment and Decrement events to update the state.

Conclusion

The single state class approach in BLoC simplifies state management by consolidating all possible states into one class. This not only reduces the complexity of managing multiple state classes but also enhances the clarity and maintainability of your code. By embracing the magic of the single state class, you can create more robust and scalable Flutter applications.

Happy coding!

--

--