If you want to see the original text, please go out and turn right. This paper introduces the concepts of Streams, Bloc and Reactive Programming. Examples of theory and practice.

Difficulty: Intermediate

introduce

It took me a long time to find a way to introduce Reactive Programming, BLoC and Streams concepts. Since this can make a big difference to the way applications are built, I want a practical example to illustrate:

  • Probably don’t use them, but sometimes they can be harder to code and lower performance,
  • The benefits of using them are also
  • The effects of their use, both positive and/or negative.

Using the pseudo-application I made as an example, in a nutshell, it allows users to view a list of movies from an online catalog, filter them by genre and release date, and mark/unmark them as favorites. Of course, everything is interactive, and users can take various actions on different pages or within the same page, and the results can be observed in real time. The following animation shows the program:




image.png

When you go to this page for information about Reactive Programming, BLoC, and Streams, I’ll cover them first. After that, I’ll show you how to implement and use them in practice.

What is a Stream?

introduce

To visualize the Stream concept, we can simply imagine a Stream as a pipe with two ports, only one of which allows something to be inserted. When you insert something into a pipe, it flows through the pipe and out the other end. In Flutter,

  • the pipe is called a Stream
  • to control the Stream, we usually<upper style=”box-sizing: border-box;” >(*)</upper> use aStreamController
  • to insert something into the Stream, the StreamControllerExposes the”entrance“, called aStreamSink, accessible via the sink property
  • the way out of the Stream, is exposed by the StreamController via the streamproperty

In a Flutter,

  • The pipe is called Stream
  • To control a Stream, we usually (*) use a StreamController
  • In order toStreamInsert something in,StreamControllerA public one calledStreamSinkThe “entrance” can be passedsinkProperty access
  • StreamThe outflow mode is defined byStreamControllerthroughstreamAttributes exposed.

    (*) : I deliberately use the term “usually” because it is likely that none will be usedStreamController. However, as you’ll see in this article, I’ll just useStreamControllers.

What can a Stream convey?

All types and any type. Any type of data can be passed by Stream, from values, events, objects, collections, maps, errors, or even another Stream.

How do I know what Stream is communicating?

You only need to listen for the Stream property of the StreamController when you need to tell the Stream to deliver something.

When you define a listener, you get a StreamSubscription object. With StreamSubscription objects, you will receive notifications when a Stream changes.

As long as there is at least one active listener, the Stream starts generating events to notify the active StreamSubscription object each time:

  • Some of the data comes from streams,
  • When some error is sent to the stream,
  • When the stream is closed.

    StreamSubscriptionThe following operations are also allowed:
  • Stop listening
  • For the time being
  • restore

Is Stream just a simple pipe?

No, A Stream also allows data flowing into it to be processed before flowing out. To control the processing of data within the Stream, we use StreamTransformer, which simply:

  • A function that “captures” the flowing data inside a Stream
  • Do something with the data
  • The result of this transformation is also a Stream to which you should easily realize that you can use multiple Streamtransformers sequentially.

StreamTransformer can be used for any type of processing, for example:

  • Filtering: Filtering data based on any type of criteria,
  • Recombination: to recombine data,
  • Modification: To apply any type of modification to data,
  • Injecting data into other streams,
  • Buffer,
  • Processing: to perform any type of operation/operation on data…

The type of the Stream

There are two types of Stream.

Single subscribe to the Stream

This type of Stream only allows the use of a single listener for the entire life of the Stream.

Even after the first subscription is cancelled, you cannot listen to such streams twice.

The broadcast Stream

This is the second type of Stream, which allows any number of listeners.

Listeners can be added to a broadcast stream at any time. The new listener will receive events when it starts listening to the Stream.

The basic example

Any type of data

The first example shows a “single subscription” Stream that simply prints input data. You might see data types that don’t matter.

StreamTransformer

The second example shows a “broadcast” Stream that conveys integer values and prints only even numbers. To do this, we apply StreamTransformer to filter (line 14) the values and let only even numbers pass through.

RxDart

Now, my introduction to Streams would not be complete without a mention of RxDart.

RxDart is a Dart implementation of the ReactiveX API that extends the original Dart Streams API to conform to the ReactiveX standard.

Because it was not originally defined by Google, it uses a different vocabulary. The following table shows the correlation between Dart and RxDart:

Dart RxDart
Stream Observable
StreamController Subject

RxDart, as I mentioned earlier, inherits the native Dart Streams API and offers three major StreamController variants:

PublishSubject

The PublishSubject is a normal broadcast StreamController, with one exception when the stream returns an Observable instead of a stream.


image.png



Stream

BehaviorSubject

The BehaviorSubject is also a broadcast StreamController, which returns an Observable instead of a Stream.


image.png



PublishSubject
BehaviorSubject

ReplaySubject

ReplaySubject is also a broadcast StreamController that returns an Observable instead of a Stream.


image.png



ReplaySubject
Stream

Important note about Resources

It is good practice to always release Resources that are no longer needed. Suitable for:

  • StreamSubscription– Unsubscribe when you no longer need to listen to Stream;
  • StreamController– Close StreamController when you no longer need it;
  • The same appliesRxDart SubjectsWhen you no longer needBehaviourSubject.PublishSubject. , please turn it off.

How do I build widgets based on the data provided by the Stream?

Flutter provides a very convenient StatefulWidget called StreamBuilder.

The StreamBuilder listens on the Stream, and whenever some data outputs the Stream, it automatically rebuilds, calling its Builder callback.

The following code demonstrates how to use StreamBuilder:

StreamBuilder<T>( key: ... optional, the unique ID of this Widget... stream: ... the stream to listen to... initialData: ... any initial data, in case the stream would initially be empty... builder: (BuildContext context, AsyncSnapshot<T> snapshot){ if (snapshot.hasData){ return ... the Widget to be built based on snapshot.data } return ... the Widget to be built if no data is available }, )Copy the code

The following example mimics the default “counter” application, but we will use Stream instead of using any setState.

Note: Counter is the default generated demo of Flutter.

Explanation and description:

  • Line 24-30: We are listening to the stream, and whenever the stream outputs a new value, we will update the Text with that value;
  • Line 35: When we clickFloatingActionButton, we increment the counter and send it to the Stream through the sink; The fact that a value is injected into the stream causes the StreamBuilder listening to it to rebuild and “flush” the counter;
  • We don’t need the concept of State anymore, everything passesStreamTo receive;
  • This is a big improvement because of the callsetState()Method forces the entire Widget (and any child widgets) to be rebuilt. Here, just rebuild the StreamBuilder (and child widgets, of course);
  • The only reason we’re still using statefulWidgets for our pages is because we need to dispose the StreamController, line 15;

What is responsive programming?

Reactive programming is programming with asynchronous data streams. In other words, from events (e.g., clicks), variable changes, messages,…… Up to the build request, everything that might change or happen will be delivered, triggered by the data flow.

Obviously, all of this means that by responding to response programming, the application will:

  • It becomes asynchronous,
  • Built around the concepts of Streams and Listeners,
  • When something happens somewhere (events, changes in variables……) Will send a notification to the Stream,
  • If “someone” listens to the Stream, it will be notified and will take appropriate action, regardless of its location in the application.

There is no longer tight coupling between components.

In short, when a Widget sends content to a Stream, the Widget no longer needs to know:

  • What’s going to happen next,
  • Who might use this information (not one, one or several widgets…)
  • Where this information might be used (nowhere, on the same page, another page, or several pages…) .
  • When this information can be used (almost directly, after a few seconds, never……) .

. Widget only cares about its own business, period!

At first glance, reading this, it might seem that this could lead to an “uncontrollable” application, but as we shall see, the opposite is true. It gives you:

  • The opportunity to build a partial application that is only responsible for a specific activity,
  • Easily simulate the behavior of some components to allow more complete test coverage,
  • Easily reuse components (either in the current application or elsewhere in another application),
  • Redesigning applications and being able to move components from one place to another without much refactoring,
  • . We will soon see the benefits of using reactive programming…… But before that I need to talk about one last topic: BLoC pattern.

BLoC mode

BLoC mode was designed by Paolo Soares and Cong Hui from Google and first demonstrated during the 2018 DartConf (23-24 January 2018). Watch this video on YouTube.

BLoC stands for Business Logic Component.

In short, Business Logic requires:

  • To one or more BLoC,
  • Remove from the presentation layer whenever possible. In other words, UI components should only care about UI things and not business,
  • Rely exclusively on Streams to use inputs and outputs,
  • Keep the platform independent,
  • Keep your environment independent. In fact, BLoC mode was originally conceived to allow the reuse of the same code independent of the platform: Web applications, mobile applications, back-end.

What does it really mean?

BLoC mode makes use of the concept we discussed earlier: Streams.




image.png

  • The Widgets send the event to BLoC through Sinks,
  • BLoC notifies Widgets via Stream,
  • The business logic implemented by BLoC is not their concern.

From above, we can immediately see a huge benefit of using BLoC.

Thanks for the separation of business logic and UI:

  • We can change the business logic at any time with minimal impact on the application,
  • We might change the UI without any impact on the business logic,
  • Testing business logic is now much easier.

How to apply this BLoC mode to Counter application?

Applying BLoC mode to the Counter app may seem like overkill, but let me show you…… first

I’ve heard you say “wow…… Why all this? Is all this necessary? “

First, separation of responsibilities

If you check CounterPage (lines 21-45), you will find absolutely no business logic in it.

This page is now only responsible for:

  • Display counters that now refresh only when necessary (even if the page doesn’t have to know)
  • Provides a button that, when pressed, will request an action on the Counter panel

In addition, the entire business logic is centralized in a separate class “IncrementBloc”.

Now if you need to change the business logic, you simply update the method _handleLogic (lines 77-80). Perhaps the new business logic will require very complex things…… CounterPage will never know about it, which is very good!

Second, testability

Testing business logic is now much easier.

No more testing the business logic through the UI. We just need to test IncrementBloc.

Third, free organization and layout

Thanks to Streams, you can now organize your layout independently of your business logic.

You can start any operation from anywhere in the application: just call. IncrementCounter Sink.

You can display counter anywhere on any page by listening to.outCounter Stream.

Fourth, reduce the number of “builds”

Using StreamBuilder instead of setState() drastically reduces the number of “builds”.

From a performance perspective, this is a huge step forward.

There’s only one limitation… Accessibility of BLoC

In order for all this to work, BLoC needs to be accessible.

There are several ways to access it:

  • Through the global singleton

    This can be done, but it’s not really recommended. In addition, since there is no class destructor in Dart, you can never properly free resources.

  • As a local variable

    You can instantiate a local instance of BLoC. In some cases, this solution perfectly meets some requirements. In this case, you should always consider initializing in the StatefulWidget so that you can use the Dispose () method to free related resources.

  • Provided by the parent

    The most common way to make it accessible is through the parent Widget, which is implemented through the StatefulWidget.

The following code shows an example of a common BlocProvider.

Some explanations about this generic BlocProvider

First, how do you use it as a provider?

If you look at the sample code “streams_4.dart”, you will see the following lines (lines 12-15)

 home: BlocProvider<IncrementBloc>(
          bloc: IncrementBloc(),
          child: CounterPage(),
        ),
Copy the code

With this code, we simply instantiate a new BlocProvider that will handle an IncrementBloc and present CounterPage as a subitem.

From that point on, any Widget in the subtree starting with BlocProvider will be able to access IncrementBloc by code:

IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);
Copy the code

Can you use multiple BLoC?

Of course, this is highly desirable. The suggestions are as follows:

  • (if there is any business logic) There is a BLoC at the top of each page,
  • Why not ApplicationBloc to handle application state?
  • Each “sufficiently complex component” has a corresponding BLoC. The following sample code displays the ApplicationBloc at the top of the entire application, and then IncrementBloc at the top of the CounterPage.

The example also shows how to retrieve two bloc.

Why not use the InheritedWidget?

In most articles related to BLoC, you’ll see providers implemented via InheritedWidget.

Of course, nothing prevents this type of implementation. However,

  • An InheritedWidget does not provide any dispose methods, and remember that it is good practice to always release resources when they are no longer needed.
  • Of course, there’s nothing to stop you from wrapping an InheritedWidget in another StatefulWidget, but what does using an InheritedWidget add?
  • Finally, if left unchecked, using inheritedWidgets can often cause side effects (see Reminder on inheritedWidgets below).

These three points explain why I chose to implement BlocProvider through StatefulWidget, which allows me to release related resources when I dispose the Widget.

Unfortunately, Flutter cannot instantiate generic types and we must pass an instance of BLoC to the BlocProvider. In order to enforce the Dispose () method in each BLoC, all BLoC must implement the BlocBase interface.

Some reminders from the InheritedWidget

In the use of InheritedWidget and through the context. InheritFromWidgetOfExactType (…). This method automatically registers the current “context” (= BuildContext) into the Widget to be rebuilt whenever the InheritedWidget’s parent or child layout changes.

The type of Widget (Stateful or Stateless) linked to BuildContext is irrelevant.

Personal advice about BLoC

The third rule related to BLoC is “dependent on Streams’ exclusive use of inputs and outputs”.

My personal experience is slightly related to this statement…… Let me explain.

Originally, BLoC mode was conceived to share the same code across platforms (AngularDart,…). “, and from this perspective, the statement makes a lot of sense.

However, if you are only going to develop a Flutter application, then in my humble experience, this is overkill.

If we stick to this statement, then there are no getters or settrs, only sinks and streams. The downside is that “all of this is asynchronous”.

Let’s look at two samples to illustrate the disadvantages:

  • You need to retrieve some data from BLoC to use as input to a page that should display these parameters immediately (think of a parameter page, for example), which would make building asynchronous pages (complicated) if we had to rely on Streams. Example code to make this work with Streams might look like this…… Ugly is not it.
  • At the BLoC level, you also need to transform a “fake” injection of some data to trigger the delivery of the data you want to receive through the stream. Example code to make this work could be: I don’t know about you, but personally, if I don’t have any limitations related to code migration/sharing I find it too cumbersome and WOULD rather use regular getters/setters when needed and Streams/Sinks to keep the separation of responsibility and broadcast information where needed, which is great.

Now is the time to see all this in practice……

As mentioned at the beginning of this article, I built a pseudo-application to show how to use all of these concepts. The full source code can be found on Github.

Indulge, because this code is far from perfect, could probably do better and/or have better architecture, but the only goal is to show you how it all works.

Because there is so much source code, I will only explain the main ones.

The source of the movie catalog

I use the free TMDB API to get a list of all the movies, along with posters, ratings and descriptions.

In order to be able to run this sample application, you need to register and obtain the API key (completely free), and then place your API key in the file “/ API /tmdb_api.dart” at line 15.

Application architecture

The application uses:

  • 3 main BLoC:

    1. ApplicationBloc(on top of everything else), responsible for providing a list of all movie genres;

    2.FavoriteBloc(right below), deals with the concept of “favorites”;

    3.MovieCatalogBloc(on the 2 main pages), responsible for providing a list of movies based on filters;
  • Six pages:

    1.HomePage: Login page, allowing navigation to 3 sub-pages;

    2.ListPage: Lists movies as GridView pages, allowing filtering, favorites selection, access to favorites, and displaying movie details in subsequent pages;

    3.ListOnePage: Similar to ListPage, but the list of movies is displayed as a horizontal list, with details below;

    4. FavoritesPage: a page that lists favorites, allowing unselection of any favorites;

    5.* Filters: Allows defining EndDrawer: genre and min/Max release date of filters. Call this page from ListPage or ListOnePage;

    6.
    The page is only called by the ListPage to display the Details of the movie, but also allows you to select/deselect the movie as a favorite;
  • 1 BLoC:

    1.FavoriteMovieBloc, link to MovieCardWidget or MovieDetailsWidget to handle the selection/deselect of movies as favorites
  • Five major widgets:

    1.FavoriteButton: Responsible for displaying the number of favorites in real-time, and redirecting to FavoritesPage when pressed;

    2.FavoriteWidget: Displays details of a favorite movie and allows it to unselect;

    3.FiltersSummary: Is responsible for displaying the currently defined filter;

    4.MovieCardWidget: Responsible for displaying a movie as a card, movie poster, rating and name, and an icon indicating that that particular movie’s selection is favorite;

    5.MovieDetailsWidget: Is responsible for displaying detailed information related to a particular movie and allowing it to select/deselect as a favorite.

Different BLoCs/Streams orchestration

The chart below shows how the main three BLoC are used:

  • On the left side of BLoC, which components call Sink
  • On the right, which components listen for the flow

For example, when the MovieDetailsWidget calls inAddFavorite Sink, two streams are triggered:

  • outTotalFavoritesStream forced reconstructionFavoriteButton, and
  • OutFavorites flow
    • Forced to rebuildMovieDetailsWidget(Favorite icon)
    • Forced to rebuild_buildMovieCard(Favorite icon)
    • Used to build eachMovieDetailsWidget


image.png




image.png




image.png

To observe the

Most widgets and pages are StatelessWidgets, which means:

  • SetState () for forced reconstruction is almost never used. The exceptions are:

    • From the ListOnePage, when the user clicks MovieCard, the MovieDetailsWidget is refreshed. This could also be…… driven by a stream
    • In FiltersPage, users are allowed to change filter criteria by Sink before accepting filter criteria.
  • The application does not use any inheritedWidgets

  • The application is almost 100% BLoCs/Streams driven, which means that most widgets are independent of each other and their location within the application

A practical example is FavoriteButton, which shows the number of favorites selected in the badge. The application has three FavoriteButton instances, each showing on three different pages.

Show movie List (tips for displaying infinite list)

To display a list of movies that fit the filter criteria, we use gridView.Builder (ListPage) or ListView.Builder (ListOnePage) as an infinite scrolling list.

Movies are retrieved through the TMDB API, pulling 20 at a time.

As a reminder, both GridView. Builder and ListView. Builder take itemCount as input, and if the number of items is provided, it means that the list is displayed based on the number of itemCount. ItemBuilder indexes range from 0 to ItemCount-1.

As you’ll see in the code, I randomly added more than 30 to the GridView.Builder. The reason is that, in this example, we are manipulating a supposedly infinite number of projects (which is not entirely true but who cares about this example). This forces the GridView.Builder request to display “up to 30” projects.

Additionally, gridView. Builder and ListView. Builder only call itemBuilder when they think an item (index) must be rendered in the viewport.

MovieCatalogBloc. OutMoviesList returns a List < MovieCard >, it be iterative to build each Movie Card. The first time, the List

is empty, but because itemCount:… + 30, we cheat the system, it will require passing _buildMovieCard (…) Present 30 items that do not exist.

As you’ll see in the code, this routine makes a strange call to Sink:

// Notify the MovieCatalogBloc that we are rendering the MovieCard[index] movieBloc.inMovieIndex.add(index);Copy the code

This call tells MovieCatalogBloc we want to render MovieCard [index].

Then the _buildMovieCard (…). Continue to verify that the data associated with MovieCard [index] exists. If so, then apply colours to a drawing the latter, show CircularProgressIndicator otherwise.

For StreamCatalogBloc. InMovieIndex. Add (index) calls by StreamSubscription listening, StreamSubscription converts index to a certain pageIndex digital (one page up to 20 films). The API is invoked if the corresponding page has not been retrieved from the TMDB API. After the page is retrieved, a new list of all acquired movies will be sent to _moviesController. When the GridView.Builder listens on the Stream (= movieBloc. OutMoviesList), the latter requests to rebuild the corresponding MovieCard. Now that we have the data, we can render it.

List and other links

ReactiveX provides images of the PublishSubject, BehaviorSubject, and ReplaySubject.

Some other interesting articles are worth reading:

  • Fundamentals of Dart Streams [Thomas Burkhart]
  • rx_command package [Thomas Burkhart]
  • Build reactive mobile apps in Flutter – companion article [Filip Hracek]
  • Flutter with Streams and RxDart [Brian Egan]

conclusion

It’s a long article, but there’s more to say, because for me, this is the way to unfold the Flutter application. It provides a lot of flexibility.

Stay tuned for new posts soon. Have fun coding.

Version all, reproduced please indicate the source.