Flutter: BLoC with Streams
This article will help you to get started with the BLoC pattern and the use of streams in Flutter.
/* BLoC — Business Logic Component */
Patterns and state management in Flutter can become very complex, therefore I will demonstrate the implementation of Flutters BLoC pattern using streams. BLoC helps to organize your code and separates the business logic from the UI. The BLoC wraps the business logic and takes events to access the functionality with streams. Here I use streams and sinks to send events and return a state to the UI.
The state then updates the UI with the retrieved data (snapshot) from the stream using a StreamBuilder.
Preparation
Create a new Flutter project and get the IncrementApp running, the Flutter app which is always created with a new project. Delete all comments and strip down all widgets to their bar minimum in order to reduce the complexity. Furthermore delete following from the project structure, because we will use a BloC to replace the incrementCounter.
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
Please refer to the linked Github repository (flutter_app_bloc) for code details.
Getting started
The new created Flutter project consists of a main.dart function and a page widget. I have refactored the main.dart file and excluded the widget class into a new file called LandingPage.dart.
First Steps
The page holds widgets and a widget can trigger in BloC executed events. Sounds complicated but if you go through it one by one it isn't. First we create the events which we can listen to.
Create CounterEvent.dart file
This class defines all events which we can trigger from the widgets.
abstract class CounterEvent{}
class IncrementEvent extends CounterEvent{}
Create a CounterBLoC.dart file
The BLoC wraps the business logic and the exposed stream. The count() function will increment a _counter, triggered from the IncrementEvent.
// CounterBloc.dart class CounterBLoC{
int _counter = 0; // to be updated with a StreamSink!
_count(CounterEvent event) => _counter++;}
Implement Stream & Sink
To trigger and share data we can use Streams and Sinks. They are controlled with a StreamController for the events and the stream itself.
Create StreamController and Sink for the counter
// CounterBLoC.dart// init StreamController
final _counterStreamController = StreamController<int>();
StreamSink<int> get counter_sink => _counterStreamController.sink;// expose data from stream
Stream<int> get stream_counter => _counterStreamController.stream;
The controller exposes the stream and the sink (StreamSink) collects the data which will be exposed.
Create StreamController and Sink for the events
final _counterEventController = StreamController<CounterEvent>();// expose sink for input events
Sink <CounterEvent> get counter_event_sink {
return _counterEventController.sink;
}
The _counterEventController (of type StreamController) controls the stream of counter events and exposes the sink of events.
Update the CounterBLoC()._count() function
_count(CounterEvent event) => counter_sink.add(++_counter);
Add the counter incrementation to the sink, which is exposed with a StreamController.
Now it is possible to stream the counter value to a widget. The sink holds the incremented value (_counter) and the controller exposes the value in a stream. Every widget which has a implemented StreamBuilder can listen to a declared stream.
Initialize BLoC Constructor and listen to stream
CounterBLoC() { _counterEventController.stream.listen(_count); }
The listen() function creates a broadcast of streams. StreamBuilders can listen to this streams.
Clean up the BLoC
To prevent memory leaks we have to close all implemented controllers.
dispose(){
_counterStreamController.close();
_counterEventController.close();
}
BLoC and Events
Initialize the CounterBLoC and attach the IncrementEvent to the sink of the reference StreamController.
//LandingPage.dartfinal bloc = CounterBLoC();
...
onPressed: () => _bloc.counter_event_sink.add(IncrementEvent()),
...
Update UI with StreamBuilder
The StreamBuilder can listen to the exposed streams and return widgets which can hold snapshots of retrieved stream-data.
// Scaffold() widgetbody: StreamBuilder(
stream: _bloc.stream_counter,
initialData: 0,
builder: (context, snapshot) {
return Center(child: Text( snapshot.data.toString() ));
}
);
Final Clean up LandingPage.dart
@override
dispose(){
super.dispose();
_bloc.dispose();
}
The CounterBLoC creates several StreamController and they have to be closed with the implemented dispose() function.
At this point we can run and test the application. This short prototype shows the complexity of a simple logic, but with the growth of you apps it becomes more practical.
Recap
I recommend to get familiar with BLoC or any other pattern to structure your app. If your are a beginner you may use public available packages at the https://pub.dartlang.org website :
I hope this simple guide helps you to get started with the BLoC pattern and Streams in Flutter. Please see my other articles as well, if you are inserted in more informations about Flutter and mobile development.
Happy coding …
Business Blog
2D Animation Repository
Github Repository