Introduction To Flutter Bloc
Flutter is always been wonderful until and unless our project turns into Spaghetti code, this is where things start to fall apart, sometimes out of our control, besides this coding a large Widget tree and being uncareful with the implementation of setState results in rebuilding our whole tree which creates a compromise with the quality of app we build, as a solution it is recommended to split out widget tree into small Stateless widgets and using const to avoid rebuilding the widgets that should not be rebuilding. As this situation arises Bloc pattern comes to play.
Bloc simply helps us to separate our UI from Business Logic and maintains the separation of concerns.
Before diving into our Bloc pattern we need to explore some topics related to streams in Flutter.
- Stream<T>
This is the source of asynchronous data events. A stream provides a way to receive a sequence of events. Each event is either a data event or an error event.
There are two kinds of streams Single-Subscription streams and Broadcast streams. A Single-subscription stream allows only one listener during the whole lifetime of the stream. it stops sending events when the listener is unsubscribed. If we provide more listeners or try to access the Single-Subscription stream after the subscription this would result in a bad state.
2. StreamBuilder
This widget takes two important arguments first is a stream and second is a builder, stream watches the stream assigned to this widget, and anytime the stream has a new value builder mth runs, The builder method takes two arguments first is context and second is AsyncSnapshot<T> (context, snapshot) here snapshot contains info for an event that just came through a stream, So we can rerender the new value of snapshot.
3. StreamController<T>
This class helps to create a controller that has its own stream to be controlled. This helps us to create a stream that can be listened to, and also a sink to add our events.
So let's look at our task.
Now let’s see the analogy of this bloc pattern.
Our purple Oval is our UI layer the thing only to be focused on here is UI, listening to our streams, and adding events to stream.
Here our business logic will be in a separate class called Bloc. This class will consist of a single StreamController.
Our type of StreamController<T> will be dynamic this is just to show the implementation of how we can use different data types only for the experiment, it can be purely any of the datatypes. Here our stream controller will be private and we are going to expose it by getters and setters.
class Bloc {final _controller = StreamController<dynamic>.broadcast();int value = 0;Stream<dynamic> get valueStream => _controller.stream;Function(dynamic) get addEvent => _controller.sink.add;}
Here we can declare the stream as a single- subscription rather than a broadcast as we have only one StreamBuilder to listen to, However, if we duplicate the same StreamBuilder twice there would be an error “Bad state: Stream has already been listened to.”, so if ever our app requires to listen to a stream at multiple widgets we need to declare this as a broadcast stream. For now, we will keep this as a broadcast.
Now as with the other controllers in Flutter even this controller needs to be closed to avoid memory leaks.
void closeStream() {_controller.close();}
Let’s add some more methods here.
void incrementValue() {value = value + 1;addEvent(value);}void decrementValue() {value = value - 1;addEvent(value);}void incrementValueBy10() {value = value + 10;addEvent(value);}void decrementValueBy10() {value = value - 10;addEvent(value);}void addErrorToStream() {_controller.addError("Streaming error");}void addString() {_controller.add("HELLO WORLD");}
The reason we made this controller Dynamic is just because we need to add a String in our implementation.
Now let's move to our UI
Here we are using the Stateful widget as we need to initialize this class and also to dispose of our controller
class MyHomePage extends StatefulWidget {@override_MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {Bloc bloc;
@overridevoid initState() {bloc = Bloc();super.initState();}@overridevoid dispose() {bloc.closeStream();super.dispose(); }}
Our widget tree simply consists of a Scaffold with a Column which will have our OutputDisplay Stateless widget with other 6 Raised buttons for each of the logic mth we defined in our bloc class in a Wrap widget.
OutputDisplay Stateless widget has a property stream which we are going to use in our StreamBuilder now depending on the various condition we define our builder mth of StreamBuilder.
StreamBuilder<dynamic>(initialData: "Getting Started",stream: stream,builder: (context, snapshot) {if (snapshot.hasError) return Text(snapshot.error, style: style);if (snapshot.data == null)return Text("Null",style: style,);if (snapshot.hasData) return Text("${snapshot.data}", style: style2);return Text("Something went wrong", style: style);});
Here we have provided an extra property of initial data this is to display the data at first if our stream does not contain any data this is the reason we see “Getting Started ” when we run our app for the first time.
Now let's look at our Stateless widget tree.
@overrideWidget build(BuildContext context) {print("I am Child Rebuilding ");final style = const TextStyle(backgroundColor: Colors.red, color: Colors.yellow, fontSize: 40);final style2 = const TextStyle(fontSize: 40,fontWeight: FontWeight.bold,backgroundColor: Colors.green,letterSpacing: 2.0);return StreamBuilder<dynamic>( ...........
here we don’t need to initialize the style and style2 again and again as it’s gets done only once when we create our stateless widget tree Now the only rebuilding widget will be the yellow portion in our diagram that’s our StreamBuilder.
With this, we have completed our BlocPattern by achieving the separation of UI with Business logic and avoiding unnecessary rebuilds.
Let’s see our code.
In the end, keep practicing as repetition is key to perfection.