Flutter: BLoC with Streams

Tino Kallinich
Flutter Community
4 min readFeb 26, 2019

--

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.

bloc_app: project structure

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

--

--