INTRO: BLoC pattern and reactive programming

As the BLoC pattern is the advised pattern for Flutter apps, I’ve decided to spend some time to understand it and adopt it for my apps.

If you want to know more about reactive programming, streams and BLoC pattern, you can check out these:

Step 1 — Model

In the first step, I’m going to define an enum to handle the scores, a “Choice” class and a List of type Choice to define all the choices and their scores.

Step 2 — BLoC pattern

In the next step step I’m going to create a class (LiverBloc) where I put all the logic of the calculator. First I need to add “RxDart” as a dependency in the pubspec.yaml file, in order to use the “RxDart” library.

You can find more info about RxDart here: https://pub.dartlang.org/packages/rxdart

RxDart provides different kinds of streams: PublishSubject, BehaviorSubject, and ReplaySubject. PublishSubject is the same of StreamController of Dart streams API. So when a listener subscribes the stream it doesn’t receive the past items but only that ones subsequent the subscription. With the BehaviorSubject instead, the listener receives the last items prior to subscription and finally with the ReplaySubject the listener receives all the items sent to stream.

For the purposes of the calculator, I define a BehaviorSubject stream (so I have the last item) of type “Score” for every item of the Child-Pugh score. In this case, inBilirubin is used to put data to stream and outBiliribun to get the associated stream.

final _bilirubin = BehaviorSubject<Score>();

Function(Score) get inBilirubin => _bilirubin.sink.add;

Stream<Score> get outBilirubin => _bilirubin.stream;

I define another stream of type integer for the result:

final _result = BehaviorSubject<int>();

Function(int) get inResult => _result.sink.add;

Stream<int> get outResult => _result.stream;

In order to compute the calculation only when all the items were selected (and then sent to respective streams), I use the combineLatest method that emits a value only when all streams we pass as parameters emitted at least one item (the user selected at least one time a choice for every item).

detailed info about combineLatest here: https://pub.dartlang.org/documentation/rxdart/latest/rx/Observable/combineLatest2.html):

Stream<bool> get isComplete => Observable.combineLatest5(

outBilirubin,

outAlbumin,

outAscites,

outInr,

outEncephalopathy,

(a, b, c, d, e) => true);

Then I add a listener to isComplete observable in the constructor of the BLoC, so that only when all the items were selected it emits a value (that I don’t need here, so I use the underscore) and fires the calc() method of the class:

LiverBloc() {

print(‘ — — — -LIVER BLOC INIT — — — — ‘);

isComplete.listen((_) {

print(‘ — — CALC CHILD PUGH — -’);

calc();

});

}

And finally, I define the “calc()” method to calculate the result. I define a List of type Score and I add to it the last item emitted for every item of the score. Then with a foreach I compute the result of the score and send it to stream by the inResult function. To close all the streams I define a dispose method.

This is the complete LiverBloc class:

Step 3 — The user interface

First of all, I initialize the bloc, then I create a scaffold and in its body, I put a Column widget with two widgets. The first widget “childPughResult” shows the result while the other holds the form (in an expanded widget so it fills the remaining height). In this way, the result widget stays at the top of the screen even when the user scrolls down.

The form

Let’s begin to talk about the “childPughForm” widget. The _buildHeader is just a helper to create the header avoiding to repeat the same code for every item. It takes a string as a parameter and creates a box with background blue and the border blueGrey. Then there is a widget for every item of the score. For the sake of simplicity, the first of them “_bilirubin()” is created in a more explicit way, while the others will be dynamically built:

To build the others items I created a “_buildChoice” helper that takes the handler function as a parameter (to pass it to the onTap event of the InkWell), a string for the choice, the score associated with it and the stream. When the score of the choice is the same of the stream passed it means that it is the selected choice so it shows a checked box instead of an empty square. The “_buildChoicesGroup” helper creates the group of choices.

The result

This is the widget I created to show the result. When the stream isComplete emits an item the property “snapshot.hasData” is true and I show the result of the score, otherwise, I show a Text widget with a text to tell user to complete the form. When the “snapshot.hasData” is true (the user has filled the form) then I pass the result to the “_buildResult” method to show the result: