Flutter: Abstraction In BLoC

Comprehensive guideline to apply Abstraction to Flutter BLoC

Mahdi Shahbazi
Flutter Community
4 min readMay 8, 2023

--

Photo by Artur Shamsutdinov on Unsplash

As a Flutter developer, you may have heard of the BLoC (Business Logic Component) pattern and its usefulness in building complex applications. However, to truly level up your development skills, it’s important to understand how to implement Abstraction with Flutter BLoC. In this guide, I’ll explore the concept of abstraction and how it can be used in conjunction with BLoC to create more scalable, maintainable, and testable code.

Abstraction

Let’s start with a quote from John Guttag:

The essence of abstraction is preserving information that is relevant in a given context, and forgetting information that is irrelevant in that context.

In simple words, Abstraction is separating related topics and looking at them separately from non-related topics (It got even more complex 🫤).

Abstraction is one of the most important concepts in software engineering which make its base topic on different architectures like Clean, MVP, and … . Using abstraction gives independence between layers which makes them easy to replace and different part of apps uncoupled from each other

BLoC Architecture

If you are familiar with BLoC you may have seen this image

From flutter_bloc https://bloclibrary.dev/#/architecture

The BLoC pattern focuses on separating business logic from the UI, using two main principles:

  1. Streams: Streams handle widget state changes, ensuring that any changes to widget state come through streams. Widgets only listen to these streams to receive updates.
  2. Events: All interactions between the widget and BLoC should use events. Widgets should only send events to BLoC and wait for updates through streams.

Abstraction in BLoC

In the BLoC pattern, the Abstraction concept handles using events and streams. There is 2 simple rule to achieve this abstraction:

  1. When you want to send anything from Bloc to Widget, always use stream and when Widget needs to get any information from Bloc, always listen to a stream
  2. When Widget needs to send an action or ask for a change from Bloc, always send it through an event

By following these 2 rules, we can use an abstraction layer between Widget and Bloc.

Abstraction with flutter_bloc

Flutter_bloc is the most popular, powerful, and predictable Flutter package for state management. Flutter_bloc is one of the best ways to implement BLoC architecture in Flutter.

Now let’s see how we should implement Abstraction (Or simply follow our rules) using flutter_bloc:

Streams:

The first rule was to receive changes through streams. To implement this flutter_bloc has some powerful Widgets like BlocBuilder , BlocListener , BlocConsumer ,… . Using these Widgets you will inform that my Widget would expect what type of State from which type of Bloc. So your widget won’t know why and when I will receive a new state.

class TestState {
final String text;

const TestState(this.text);
}

abstract class TestEvent {}

class TestBloc extends Bloc<TestEvent, TestState> {
TestBloc() : super(const TestState(''));
}

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

@override
Widget build(BuildContext context) {
return BlocBuilder<TestBloc,TestState>(
builder: (_, state) {
return const SizedBox.shrink();
},
);
}
}

In this example, we can see the TestWidget connected to BLoC through an abstract class called Bloc. Here BlocBuilder needs an instance of Bloc which will find it in its parent widget using context.read<TextBloc>()

Events:

For the second rule, we will go for events. With flutter_bloc we will have a method call add in our Blocs which gets an event as input. This event will be sent to Bloc to handle it and update the state if needed. Again, the Bloc will not know why and when I will receive a new event.

class CTATestEvent implements TestEvent {}

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

@override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: context.read<TestBloc>(),
builder: (_, state) {
return InkWell(
onTap: () {
context.read<TestBloc>().add(CTATestEvent());
},
child: const SizedBox.shrink(),
);
},
);
}
}

Now in the same example, we can send an event from widget to bloc using add method. We are using the same method to find Bloc from the widget tree and pass the event.

These 2 rules will help us to remove the direct connection between Widget and Bloc.

WARNING

warning

There is an anti-pattern against abstraction which always comes with flutter_bloc. While using flutter_bloc you can access bloc inside the Widget using context.read<BLOC_TYPE>(). This gives developers the ability to have access to bloc methods and properties through widgets. This is wrong since we are connecting these 2 classes to each other without using stream and event and now replacing any of them would be harder.

Thanks for reading.

Flutter Level UP

Flutter Level UP is a resource to improve your Flutter skills. If you have some experience with Flutter and want to improve, check out the list on my page and follow for updates.

--

--