Uploaded by dodovor716

Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases

advertisement
Reactive Programming - Streams - BLoC - Practical
Use CasesBlog
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
December 01, 2018
BLoC, Reactive Programming, Streams - Practical use cases and useful patterns.
Difficulty: Intermediate
Introduction
(This version has been adapted to Flutter version 1.12.1).
Following the introduction to the notions of BLoC, Reactive Programming and Streams, I made some time ago, I though it
might be interesting to share with you some patterns I regularly use and personally find very useful (at least to me). These
patterns make me save a tremendous amount of time in my developments and also make my code much easier to read and
debug.
The topics I am going to talk are:
BLoC Provider and InheritedWidget
Where to initialize a BLoC ?
Event-State
Allows to respond to State transition, based on Event.
Form Validation
Allows to control the behavior of a Form based on entries and validation(s).
(The solution I explain also covers the comparison of the password and retyped password).
Part Of
Allows a Widget to adapt its behavior based on its presence in a list.
The complete source code is available on GitHub.
1. BLoC Provider and InheritedWidget
I take the opportunity of this article to introduce another version of my BlocProvider, which now relies on an InheritedWidget.
The advantage of using an InheritedWidget is that we gain in performance.
Let me explain…
1.1. Previous Implementation
My previous version of the BlocProvider was implemented as a regular StatefulWidget, as follows:
1
abstract class BlocBase {
2
3
void dispose();
}
4
5
// Generic BLoC provider
6
class BlocProvider<T extends BlocBase> extends StatefulWidget {
7
BlocProvider({
8
Key key,
9
@required this.child,
10
11
@required this.bloc,
}): super(key: key);
12
13
final T bloc;
14
final Widget child;
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
1/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
15
16
@override
17
_BlocProviderState<T> createState() => _BlocProviderState<T>();
18
19
static T of<T extends BlocBase>(BuildContext context){
20
final type = _typeOf<BlocProvider<T>>();
21
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
22
return provider.bloc;
23
}
24
25
26
static Type _typeOf<T>() => T;
}
27
28
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
29
@override
30
void dispose(){
31
widget.bloc.dispose();
32
super.dispose();
33
}
34
35
@override
36
Widget build(BuildContext context){
37
return widget.child;
38
39
}
}
bloc_provider_previous.dart hosted with ❤ by GitHub
view raw
I used a StatefulWidget to benefit from its dispose() method to ensure the release of the resources allocated by the BLoC, when
no longer necessary.
This works just fine but it is not optimal from a performance perspective.
The context.ancestorWidgetOfExactType() is a O(n) function. In order to retrieve the requested ancestor that corresponds to a
certain type, it navigates up the tree, starting from the context and recursively goes up one parent at a time, until completion. If
the distance from the context to the ancestor is small, the call to this function is acceptable, otherwise it should be avoided.
Here is the code of this function.
@override
Widget ancestorWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
1.2. New Implementation
(This version has been adapted to Flutter version 1.12.1).
The new implementation relies on a StatefulWidget, combined with an InheritedWidget:
1
import 'package:flutter/material.dart';
2
3
typedef BlocBuilder<T> = T Function();
4
typedef BlocDisposer<T> = Function(T);
5
6
abstract class BlocBase {
7
8
void dispose();
}
9
10
11
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
12
Key key,
13
@required this.child,
14
@required this.blocBuilder,
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
2/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
15
this.blocDispose,
16
}): super(key: key);
17
18
final Widget child;
19
final BlocBuilder<T> blocBuilder;
20
final BlocDisposer<T> blocDispose;
21
22
@override
23
_BlocProviderState<T> createState() => _BlocProviderState<T>();
24
25
static T of<T extends BlocBase>(BuildContext context){
26
_BlocProviderInherited<T> provider = context.getElementForInheritedWidgetOfExactType<_BlocProviderInherited<T>>()?.wid
27
28
return provider?.bloc;
29
30
}
}
31
32
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>>{
33
34
T bloc;
35
36
@override
37
void initState() {
38
super.initState();
39
bloc = widget.blocBuilder();
40
}
41
42
@override
43
void dispose(){
44
if (widget.blocDispose != null){
45
widget.blocDispose(bloc);
46
} else {
47
bloc?.dispose();
48
}
49
super.dispose();
50
}
51
52
@override
53
Widget build(BuildContext context){
54
return new _BlocProviderInherited<T>(
55
bloc: bloc,
56
child: widget.child,
57
);
58
59
}
}
60
61
class _BlocProviderInherited<T> extends InheritedWidget {
62
_BlocProviderInherited({
63
Key key,
64
@required Widget child,
65
@required this.bloc,
66
}) : super(key: key, child: child);
67
68
final T bloc;
69
70
@override
71
bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
72
}
bloc_provider_new.dart hosted with ❤ by GitHub
view raw
The advantage is this solution is performance.
Thanks to the use of an InheritedWidget, it may now call the context.getElementForInheritedWidgetOfExactType() function,
which is a O(1), meaning that the retrieval of the ancestor is immediate.
This comes from the fact that all InheritedWidgets are memorized by the Framework.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
3/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Why using the getElementForInheritedWidgetOfExactType ?
You might have noticed that I use the getElementForInheritedWidgetOfExactType method instead of the new method
dependOnInheritedWidgetOfExactType.
The reason comes from the fact that I do not want the context that invokes the BlocProvider to be registered as a dependency
of the InheritedWidget since I do not need it.
1.3. How to use the new BlocProvider ?
1.3.1. Injection of the BLoC
Widget build(BuildContext context){
return BlocProvider<MyBloc>{
blocBuilder: () => myBloc,
child: ...
}
}
1.3.2. Retrieval of the BLoC
Widget build(BuildContext context){
MyBloc myBloc = BlocProvider.of<MyBloc>(context);
...
}
2. Where to initialize a BLoC
To answer this question, you need to figure out the scope of its use.
2.1. Available everywhere in the Application
Suppose that you have to deal with some mechanisms related to User Authentication/Profile, User Preferences, Shopping
Basket… anything that would require a BLoC to be available from potentially any possible parts of an application (eg from
different pages), there exists 2 ways of making this BLoC accessible.
2.1.1. Use of a Global Singleton
This solution relies on the use of a Global object, instantiated once for all, not part of any Widget tree.
1
import 'package:rxdart/rxdart.dart';
2
3
class GlobalBloc {
4
///
5
/// Streams related to this BLoC
6
///
7
BehaviorSubject<String> _controller = BehaviorSubject<String>();
8
Function(String) get push => _controller.sink.add;
9
Stream<String> get stream => _controller;
10
11
///
12
/// Singleton factory
13
///
14
static final GlobalBloc _bloc = new GlobalBloc._internal();
15
factory GlobalBloc(){
16
return _bloc;
17
}
18
GlobalBloc._internal();
19
20
///
21
/// Resource disposal
22
///
23
void dispose(){
24
25
_controller?.close();
}
26
27
GlobalBloc globalBloc = GlobalBloc();
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
4/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
bloc_singleton.dart hosted with ❤ by GitHub
view raw
To use this BLoC, you simply need to import the class and directly invoke its methods as follows:
1
2
import 'global_bloc.dart';
3
4
5
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
6
7
8
9
globalBloc.push('building MyWidget');
return Container();
}
}
This is an acceptable solution if you need to have a BLoC which is unique and needs to be accessible from anywhere inside the
application.
it is very easy to use;
it does not depend on any BuildContext;
no need to look for the BLoC via any BlocProvider and,
in order to release its resources, simply make sure to implement the application as a StatefulWidget and invoke the
globalBloc.dispose() inside the overridden dispose() method of the application Widget.
Many purists are against this solution. I don’t know really why but… so let’s look at another one…
2.1.2. Position it on top of everything
In Flutter, the ancestor of all pages must itself be the parent of the MaterialApp. This is due to the fact that a page (or Route) is
wrapped in an OverlayEntry, child of a common Stack for all pages.
In other words, each page has a Buildcontext which is independent of any other page. This explains why, without using any
tricks, it is impossible for 2 pages (Routes) to have anything in common.
Therefore, if you need a BLoC to be available anywhere in the application, you have to put it as the parent of the MaterialApp,
as follows:
1
void main() => runApp(Application());
2
3
class Application extends StatelessWidget {
4
@override
5
Widget build(BuildContext context) {
6
return BlocProvider<AuthenticationBloc>(
7
blocBuilder: () => AuthenticationBloc(),
8
blocDispose: (AuthenticationBloc bloc) => bloc?.dispose(),
9
child: MaterialApp(
10
title: 'BLoC Samples',
11
theme: ThemeData(
12
primarySwatch: Colors.blue,
13
),
14
home: InitializationPage(),
15
),
16
);
17
18
}
}
bloc_on_top.dart hosted with ❤ by GitHub
view raw
2.2. Available to a sub-tree
Most of the time, you might need to use a BLoC in some specific parts of the application.
As an example, we could think of a discussion thread where the BLoC will be used to
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
5/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
interact with the Server to retrieve, add, update posts
list the threads to be displayed in a certain page
…
For this example, you do not need this BLoC to be available to the whole application but to some Widgets, part of a tree.
A first solution could be to inject the BLoC as the root of the Widgets tree, as follows:
1
class MyTree extends StatelessWidget {
2
@override
3
Widget build(BuildContext context){
4
return BlocProvider<MyBloc>(
5
blocBuilder: () => MyBloc(),
6
child: Column(
7
children: <Widget>[
8
MyChildWidget(),
9
],
10
),
11
);
12
13
}
}
14
15
class MyChildWidget extends StatelessWidget {
16
@override
17
Widget build(BuildContext context){
18
MyBloc = BlocProvider.of<MyBloc>(context);
19
return Container();
20
21
}
}
bloc_init_root.dart hosted with ❤ by GitHub
view raw
This way, all widgets will access the BLoC, via a call to the BlocProvider.of method.
2.3. Available to only one Widget
This relates to cases where a BLoC would only be used by only one Widget.
In this case, it is acceptable to instantiate the BLoC inside the Widget.
3. Event State
Sometimes, handling a series of activities which might be sequential or parallel, long or short, synchronous or asynchronous and
which could also lead to various results, can become extremely hard to program. You might also require to update the display
along with the progress or depending on the states.
This first use case is aimed at making it easier to handle such situation.
This solution is based on the following principle:
an event is emitted;
this event triggers some action which leads to one or several states;
each of these states could in turn emit other events or lead to another state;
these events would then trigger other action(s), based on the active state;
and so on…
In order to illustrate this concept, let’s take 2 common examples:
Application initialization
Suppose that you need to run a series of actions to initialize an application. Actions might be linked to interactions with a
server (eg to load some data).
During this initialization process, you might need to display a progress bar together with a series of images to make the
user wait.
Authentication
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
6/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
At start up, an application might require a user to authenticate or to register.
Once the user is authenticated, the user is redirected to the main page of the application. Then, if the user signs out, he is
redirected to the authentication page.
In order to be able to handle all possible cases, sequence of events but also if we consider that events could be triggered
anywhere in the application, this might become quite hard to manage.
This is where the BlocEventState, combined with a BlocEventStateBuilder, can help a lot…
3.1. BlocEventState
The idea behind the BlocEventState is to define a BLoC that:
accepts Events as input;
invokes an eventHandler when a new event is emitted;
the eventHandler is reponsible for taking appropriate actions based on the event and emitting State(s) in response.
The following picture shows the idea:
Here is the source code of such class. Explanation will follow:
1
import 'package:blocs/bloc_helpers/bloc_provider.dart';
2
import 'package:meta/meta.dart';
3
import 'package:rxdart/rxdart.dart';
4
5
abstract class BlocEvent extends Object {}
6
abstract class BlocState extends Object {}
7
8
abstract class BlocEventStateBase<BlocEvent, BlocState> implements BlocBase {
9
PublishSubject<BlocEvent> _eventController = PublishSubject<BlocEvent>();
10
BehaviorSubject<BlocState> _stateController = BehaviorSubject<BlocState>();
11
12
///
13
/// To be invoked to emit an event
14
///
15
Function(BlocEvent) get emitEvent => _eventController.sink.add;
16
17
///
18
/// Current/New state
19
///
20
Stream<BlocState> get state => _stateController.stream;
21
22
///
23
/// External processing of the event
24
///
25
Stream<BlocState> eventHandler(BlocEvent event, BlocState currentState);
26
27
///
28
/// initialState
29
///
30
final BlocState initialState;
31
32
//
33
// Constructor
34
//
35
BlocEventStateBase({
36
37
38
@required this.initialState,
}){
//
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
7/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
39
// For each received event, we invoke the [eventHandler] and
40
// emit any resulting newState
41
//
42
_eventController.listen((BlocEvent event){
43
BlocState currentState = _stateController.value ?? initialState;
44
eventHandler(event, currentState).forEach((BlocState newState){
45
_stateController.sink.add(newState);
46
});
47
});
48
}
49
50
@override
51
void dispose() {
52
_eventController.close();
53
_stateController.close();
54
55
}
}
bloc_event_state.dart hosted with ❤ by GitHub
view raw
As you can see, this is an abstract class that needs to be extended, to define the behavior of the eventHandler method.
It exposes:
a Sink (emitEvent) to push an Event;
a Stream (state) to listen to emitted State(s).
At initialization time (see Constructor):
an initialState needs to be provided;
it creates a StreamSubscription to listen to incoming Events to
dispatch them to the eventHandler
to emit resulting state(s).
3.2. Specialized BlocEventState
The template to use to implement such BlocEventState is given here below. Right after, we will implement real ones.
1
class TemplateEventStateBloc extends BlocEventStateBase<BlocEvent, BlocState> {
2
TemplateEventStateBloc()
3
: super(
4
initialState: BlocState.notInitialized(),
5
);
6
7
@override
8
Stream<BlocState> eventHandler( BlocEvent event, BlocState currentState) async* {
9
yield BlocState.notInitialized();
10
11
}
}
bloc_event_state_template.dart hosted with ❤ by GitHub
view raw
Do not worry if this template does not compile… This is normal as we haven’t defined the BlocState.notInitialized() yet… This will
come in a few moment.
This template simply provides an initialState at initialization and overrides the eventHandler.
There is something very interesting to note, here. We use the asynchronous generator: async* and the yield statement.
Marking a function with the async* modifier, identifies the function as an asynchronous generator:
Each time the yield statement is called, it adds the result of the expression that follows the yield to the output Stream.
This is particularly useful if we need to emit a sequence of States, resulting from a series of actions (we will see later, in
practice)
For additional details on asynchronous generators, please follow this link.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
8/37
3.3. BlocEvent and BlocState
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
As you have noticed, we have defined a BlocEvent and BlocState abstract classes.
These classes need to be extended with the specialized events and states you want to emit.
3.4. BlocEventStateBuilder Widget
Last piece of this pattern is the BlocEventStateBuilder Widget which allows you to respond to the State(s), emitted by the
BlocEventState.
Here is its source code:
1
typedef Widget AsyncBlocEventStateBuilder<BlocState>(BuildContext context, BlocState state);
2
3
class BlocEventStateBuilder<BlocEvent,BlocState> extends StatelessWidget {
4
const BlocEventStateBuilder({
5
Key key,
6
@required this.builder,
7
@required this.bloc,
8
}): assert(builder != null),
9
assert(bloc != null),
10
super(key: key);
11
12
final BlocEventStateBase<BlocEvent,BlocState> bloc;
13
final AsyncBlocEventStateBuilder<BlocState> builder;
14
15
@override
16
Widget build(BuildContext context){
17
return StreamBuilder<BlocState>(
18
stream: bloc.state,
19
initialData: bloc.initialState,
20
builder: (BuildContext context, AsyncSnapshot<BlocState> snapshot){
21
return builder(context, snapshot.data);
22
},
23
);
24
25
}
}
bloc_event_state_builder.dart hosted with ❤ by GitHub
view raw
This Widget is nothing else but a specialized StreamBuilder, which will invoke the builder input argument each time a new
BlocState will be emitted.
Okay. Now that we have all the pieces, it is time to show what we can do with them…
3.5. Case 1: Application Initialization
This first example illustrates the case where you need your application to perform some tasks at start time.
A common use is a game which initially displays a splash screen (animated or not) while it gets some files from the server,
checks if new updates are available, tries to connect to any game center… before displaying the actual main screen. In order not
to give the feeling that the application does nothing, it might display a progress bar and show some pictures at regular interval,
while it does all its initialization process.
The implementation I am going to show you is very simple. It will only display some competion percentages on the screen, but
this can be very easily extended to your needs.
The first thing to do is to define the events and states…
3.5.1. ApplicationInitializationEvent
For this example, I will only consider 2 events:
start: this event will trigger the initialization process;
stop: the event could be used to force the initialization process to stop.
Here is the definition:
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
9/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
1
class ApplicationInitializationEvent extends BlocEvent {
2
3
final ApplicationInitializationEventType type;
4
5
ApplicationInitializationEvent({
6
this.type: ApplicationInitializationEventType.start,
7
8
}) : assert(type != null);
}
9
10
enum ApplicationInitializationEventType {
11
start,
12
stop,
13
}
app_init_event.dart hosted with ❤ by GitHub
view raw
3.5.2. ApplicationInitializationState
This class will provide the information related to the initialization process.
For this example, I will consider:
2 flags:
isInitialized to indicate whether the initialization is complete
isInitializing to know whether we are in the middle of the initialization process
the progress completion ratio
Here is its source code:
1
class ApplicationInitializationState extends BlocState {
2
ApplicationInitializationState({
3
@required this.isInitialized,
4
this.isInitializing: false,
5
this.progress: 0,
6
});
7
8
final bool isInitialized;
9
final bool isInitializing;
10
final int progress;
11
12
factory ApplicationInitializationState.notInitialized() {
13
return ApplicationInitializationState(
14
isInitialized: false,
15
);
16
}
17
18
factory ApplicationInitializationState.progressing(int progress) {
19
return ApplicationInitializationState(
20
isInitialized: progress == 100,
21
isInitializing: true,
22
progress: progress,
23
);
24
}
25
26
factory ApplicationInitializationState.initialized() {
27
return ApplicationInitializationState(
28
isInitialized: true,
29
progress: 100,
30
);
31
32
}
}
app_init_state.dart hosted with ❤ by GitHub
view raw
3.5.3. ApplicationInitializationBloc
This BLoC is responsible for the handling the initialization process based on events.
Here is the code:
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
10/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
1
class ApplicationInitializationBloc
2
extends BlocEventStateBase<ApplicationInitializationEvent, ApplicationInitializationState> {
3
ApplicationInitializationBloc()
4
: super(
5
initialState: ApplicationInitializationState.notInitialized(),
6
);
7
8
@override
9
Stream<ApplicationInitializationState> eventHandler(
10
ApplicationInitializationEvent event, ApplicationInitializationState currentState) async* {
11
12
if (!currentState.isInitialized){
13
yield ApplicationInitializationState.notInitialized();
14
}
15
16
if (event.type == ApplicationInitializationEventType.start) {
17
for (int progress = 0; progress < 101; progress += 10){
18
await Future.delayed(const Duration(milliseconds: 300));
19
yield ApplicationInitializationState.progressing(progress);
20
}
21
}
22
23
if (event.type == ApplicationInitializationEventType.stop){
24
yield ApplicationInitializationState.initialized();
25
}
26
27
}
}
bloc_init_bloc.dart hosted with ❤ by GitHub
view raw
Some explanation:
when the event “ApplicationInitializationEventType.start” is received, it starts counting from 0 to 100 (step 10) and, for
each of the values (0, 10, 20, …) it emits (via the yield) a new state that tells that the initialization is running (isInitializing =
true) and its progress value.
when the event “ApplicationInitializationEventType.stop” is received, it considers that the initialization is complete.
as you can see, I put some delay in the counter loop. This shows you how you could use any Future (e.g. case where you
would need to contact the server)
3.5.4. Wrapping it all together
Now, the remaining part is to display the pseudo Splash screen that shows the counter…
1
class InitializationPage extends StatefulWidget {
2
@override
3
_InitializationPageState createState() => _InitializationPageState();
4
}
5
6
7
class _InitializationPageState extends State<InitializationPage> {
ApplicationInitializationBloc bloc;
8
9
10
@override
void initState(){
11
super.initState();
12
bloc = ApplicationInitializationBloc();
13
bloc.emitEvent(ApplicationInitializationEvent());
14
}
15
16
@override
17
void dispose(){
18
bloc?.dispose();
19
super.dispose();
20
}
21
22
@override
23
Widget build(BuildContext pageContext) {
24
25
return SafeArea(
child: Scaffold(
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
11/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
26
body: Container(
27
child: Center(
28
child: BlocEventStateBuilder<ApplicationInitializationEvent, ApplicationInitializationState>(
29
bloc: bloc,
30
builder: (BuildContext context, ApplicationInitializationState state){
31
if (state.isInitialized){
32
//
33
// Once the initialization is complete, let's move to another page
34
//
35
WidgetsBinding.instance.addPostFrameCallback((_){
36
Navigator.of(context).pushReplacementNamed('/home');
37
});
38
}
39
return Text('Initialization in progress... ${state.progress}%');
40
},
41
),
42
),
43
),
44
),
45
);
46
47
}
}
bloc_init_page.dart hosted with ❤ by GitHub
view raw
Explanation:
As the ApplicationInitializationBloc does not need to be used anywhere in the application, we can initialize it in a
StatefulWidget;
We directly emit the ApplicationInitializationEventType.start event to trigger the eventHandler
Each time an ApplicationInitializationState is emitted, we update the text
When the initialization is complete, we redirect the user to the Home page.
Trick
As we cannot directly redirect to the Home page, inside the builder, we use the
WidgetsBinding.instance.addPostFrameCallback() method to request Flutter to execute a method as soon as the rendering is
complete
3.6. Case 2: Application Authentication and Sign out
For this example, I will consider the following use-case:
at start up, if the user is not authenticated, the Authentication/Registration page is automatically displayed;
during the user authentication, a CircularProgressIndicator is displayed;
once authenticated, the user is redirected to the Home page;
anywhere in the application, the user has the possibility to log out;
when the user logs out, the user is automatically redirected to the Authentication page.
Of course, it is very possible to handle all this programmatically but it is much easier to delegate all this to a BLoC.
The following diagram explains the solution I am going to explain:
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
12/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
An intermediate page, called “DecisionPage” will be responsible for the automatic redirection of the user to either the
Authentication page or to the Home page, depending on the status of the user authentication. This DecisionPage is of course,
never displayed and should not be considered as a page, as such.
The first thing to do is to define the events and states…
3.6.1. AuthenticationEvent
For this example, I will only consider 2 events:
login: this event is emitted when the user correctly authenticates;
logout: the event is emitted when the user logs out.
Here is the definition:
1
abstract class AuthenticationEvent extends BlocEvent {
2
final String name;
3
4
AuthenticationEvent({
5
this.name: '',
6
7
});
}
8
9
class AuthenticationEventLogin extends AuthenticationEvent {
10
AuthenticationEventLogin({
11
String name,
12
}) : super(
13
name: name,
14
15
);
}
16
17
class AuthenticationEventLogout extends AuthenticationEvent {}
bloc_auth_event.dart hosted with ❤ by GitHub
view raw
3.6.2. AuthenticationState
This class will provide the information related to the authentication process.
For this example, I will consider:
3 flags:
isAuthenticated to indicate whether the authentication is complete
isAuthenticating to know whether we are in the middle of the authentication process
hasFailed to indicate that the authentication failed
the name of the user once authenticated
Here is its source code:
1
2
class AuthenticationState extends BlocState {
AuthenticationState({
3
@required this.isAuthenticated,
4
this.isAuthenticating: false,
5
this.hasFailed: false,
6
this.name: '',
7
});
8
9
final bool isAuthenticated;
10
final bool isAuthenticating;
11
final bool hasFailed;
12
13
final String name;
14
15
factory AuthenticationState.notAuthenticated() {
16
return AuthenticationState(
17
isAuthenticated: false,
18
19
);
}
20
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
13/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
21
factory AuthenticationState.authenticated(String name) {
22
return AuthenticationState(
23
isAuthenticated: true,
24
name: name,
25
);
26
}
27
28
factory AuthenticationState.authenticating() {
29
return AuthenticationState(
30
isAuthenticated: false,
31
isAuthenticating: true,
32
);
33
}
34
35
factory AuthenticationState.failure() {
36
return AuthenticationState(
37
isAuthenticated: false,
38
hasFailed: true,
39
);
40
41
}
}
bloc_auth_state.dart hosted with ❤ by GitHub
view raw
3.5.3. AuthenticationBloc
This BLoC is responsible for the handling the authentication process based on events.
Here is the code:
1
class AuthenticationBloc
2
extends BlocEventStateBase<AuthenticationEvent, AuthenticationState> {
3
AuthenticationBloc()
4
: super(
5
initialState: AuthenticationState.notAuthenticated(),
6
);
7
8
@override
9
Stream<AuthenticationState> eventHandler(
10
AuthenticationEvent event, AuthenticationState currentState) async* {
11
12
if (event is AuthenticationEventLogin) {
13
// Inform that we are proceeding with the authentication
14
yield AuthenticationState.authenticating();
15
16
// Simulate a call to the authentication server
17
await Future.delayed(const Duration(seconds: 2));
18
19
// Inform that we have successfuly authenticated, or not
20
if (event.name == "failure"){
21
yield AuthenticationState.failure();
22
} else {
23
yield AuthenticationState.authenticated(event.name);
24
}
25
}
26
27
if (event is AuthenticationEventLogout){
28
yield AuthenticationState.notAuthenticated();
29
}
30
31
}
}
bloc_auth_bloc.dart hosted with ❤ by GitHub
view raw
Some explanation:
when the event “AuthenticationEventLogin” is received, it emits (via the yield) a new state that tells that the authentication
is running (isAuthenticating = true).
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
14/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
it then runs the authentication and, once done, emits another state that tells that the authentication is complete.
when the event “AuthenticationEventLogout” is received, it emis a new state that tells that the user is no longer
authenticated.
3.5.4. AuthenticationPage
As you are going to see, this page is very basic and does not do very much, for sake of explanation.
Here is the code. Explanation will follow:
1
class AuthenticationPage extends StatelessWidget {
2
///
3
/// Prevents the use of the "back" button
4
///
5
Future<bool> _onWillPopScope() async {
6
7
return false;
}
8
9
10
@override
Widget build(BuildContext context) {
11
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
12
return WillPopScope(
13
onWillPop: _onWillPopScope,
14
child: SafeArea(
15
16
child: Scaffold(
appBar: AppBar(
17
title: Text('Authentication Page'),
18
leading: Container(),
19
),
20
body: Container(
21
22
child:
BlocEventStateBuilder<AuthenticationEvent, AuthenticationState>(
23
bloc: bloc,
24
builder: (BuildContext context, AuthenticationState state) {
25
if (state.isAuthenticating) {
26
27
return PendingAction();
}
28
29
if (state.isAuthenticated){
30
31
return Container();
}
32
33
List<Widget> children = <Widget>[];
34
35
// Button to fake the authentication (success)
36
children.add(
37
ListTile(
38
title: RaisedButton(
39
child: Text('Log in (success)'),
40
onPressed: () {
41
bloc.emitEvent(AuthenticationEventLogin(name: 'Didier'));
42
},
43
),
44
45
),
);
46
47
// Button to fake the authentication (failure)
48
children.add(
49
ListTile(
50
title: RaisedButton(
51
child: Text('Log in (failure)'),
52
onPressed: () {
53
bloc.emitEvent(AuthenticationEventLogin(name: 'failure'));
54
},
55
),
56
57
),
);
58
59
// Display a text if the authentication failed
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
15/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
60
if (state.hasFailed){
61
children.add(
62
Text('Authentication failure!'),
63
);
64
}
65
66
return Column(
67
children: children,
68
);
69
},
70
),
71
),
72
),
73
),
74
);
75
76
}
}
bloc_auth_page.dart hosted with ❤ by GitHub
view raw
Explanation:
line #11: the page retrieves the reference to the AuthenticationBloc
lines #24-70: it listens to emitted AuthenticationState:
if the authentication is in progress, it displays a CircularProgressIndicator that tells the user that something is going
on and prevents the user from accessing to the page (lines #25-27)
if the authentication is successful, we do not need to display anything (lines #29-31).
if the user is not authenticated, it displays 2 buttons to simulate a successful authentication and a failure.
when we click on one of these buttons, we emit an AuthenticationEventLogin event, together with some parameters
(which normally will be used by the authentication process)
if the authentication failed, we display an error message (lines #60-64)
That’s it! Nothing else needs to be done… Easy isn’t it?
Tip
As you may have noticed, I wrapped the page inside a WillPopScope.
The rationale is that I do not want the user to be able to use the Android ‘Back’ button as in this sample, the Authentication is a
compulsory step which prevents the user from accessing any other part unless correctly authenticated.
3.5.5. DecisionPage
As said previously, I want the application to automate the redirection to the AuthenticationPage or to the HomePage, based on
the authentication status.
Here is the code of this DecisionPage, explanation will follow:
1
class DecisionPage extends StatefulWidget {
2
@override
3
DecisionPageState createState() {
4
return new DecisionPageState();
5
6
}
}
7
8
9
class DecisionPageState extends State<DecisionPage> {
AuthenticationState oldAuthenticationState;
10
11
@override
12
Widget build(BuildContext context) {
13
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
14
return BlocEventStateBuilder<AuthenticationEvent, AuthenticationState>(
15
bloc: bloc,
16
builder: (BuildContext context, AuthenticationState state) {
17
18
if (state != oldAuthenticationState){
oldAuthenticationState = state;
19
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
16/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
20
if (state.isAuthenticated){
21
_redirectToPage(context, HomePage());
22
} else if (state.isAuthenticating || state.hasFailed){
23
//do nothing
24
} else {
25
_redirectToPage(context, AuthenticationPage());
26
}
27
}
28
// This page does not need to display anything since it will
29
// always remind behind any active page (and thus 'hidden').
30
return Container();
31
}
32
);
33
}
34
35
void _redirectToPage(BuildContext context, Widget page){
36
WidgetsBinding.instance.addPostFrameCallback((_){
37
MaterialPageRoute newRoute = MaterialPageRoute(
38
builder: (BuildContext context) => page
39
);
40
41
Navigator.of(context).pushAndRemoveUntil(newRoute, ModalRoute.withName('/decision'));
42
});
43
44
}
}
bloc_decision_page.dart hosted with ❤ by GitHub
view raw
Reminder
To explain this in details, we need to come back to the way Pages (=Route) are handled by Flutter. To handle Routes, we use a
Navigator, which creates an Overlay.
This Overlay is a Stack of OverlayEntry, each of them containing a Page.
When we are pushing, popping, replacing a page, via the Navigator.of(context), the latter updates its Overlay (so the Stack),
which is rebuilt.
When the Stack is rebuilt, each OverlayEntry (thus its content) is also rebuilt.
As a consequence, all pages that remain are rebuilt when we are doing an operation via the Navigator.of(context) !
So, why did I implement this as a StatefulWidget?
In order to be able to respond to any change of AuthenticationState, this “page” needs to remain present during the whole
lifecycle of the application.
This means that, based on the reminder here above, this page will be rebuilt each time an action is done by the
Navigator.of(context).
As a consequence, its BlocEventStateBuilder will also rebuild, invoking its own builder method.
Because this builder is reponsible for redirecting the user to the page that corresponds to the AuthenticationState, if we
redirect the user each time the page is rebuilt, it will continue redirecting forever since continously rebuilt.
To prevent this from happening, we simply need to memorize the last AuthenticationState for which we took an action and
only take another action when a different AuthenticationState is received.
How does this work?
As said, the BlocEventStateBuilder invokes its builder each time an AuthenticationState is emitted.
Based on the state flags (isAuthenticated), we know to which page we need to redirect the user.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
17/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Trick
As we cannot directly redirect to another page from the builder, we use the WidgetsBinding.instance.addPostFrameCallback()
method to request Flutter to execute a method as soon as the rendering is complete
Also, as we need to remove any existing page before redirecting the user, except this DecisionPage, which needs to remain in
all circumstances, we use the Navigator.of(context).pushAndRemoveUntil(…) to achieve this.
3.5.8. Log out
To let the user logging out, you may now create a “LogOutButton” and put it anywhere in the application.
This button simply needs to emit a AuthenticationEventLogout() event, which will lead to the following automatic chain of
actions:
1. it will be handled by the AuthenticationBloc
2. which in turn will emit an AuthentiationState (isAuthenticated = false)
3. which will be handled by the DecisionPage via the BlocEventStateBuilder
4. which will redirect the user to the AuthenticationPage
Here is the code of such button:
1
class LogOutButton extends StatelessWidget {
2
@override
3
Widget build(BuildContext context) {
4
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
5
return IconButton(
6
icon: Icon(Icons.exit_to_app),
7
onPressed: () {
8
bloc.emitEvent(AuthenticationEventLogout());
9
},
10
);
11
12
}
}
bloc_log_out_button.dart hosted with ❤ by GitHub
view raw
3.5.7. AuthenticationBloc
As the AuthenticationBloc needs to be available to any page of this application, we will also inject it as a parent of the
MaterialApp, as follows:
1
void main() => runApp(Application());
2
3
class Application extends StatelessWidget {
4
@override
5
Widget build(BuildContext context) {
6
return BlocProvider<AuthenticationBloc>(
7
blocBuilder: () => AuthenticationBloc(),
8
child: MaterialApp(
9
title: 'BLoC Samples',
10
theme: ThemeData(
11
primarySwatch: Colors.blue,
12
),
13
home: DecisionPage(),
14
),
15
);
16
17
}
}
bloc_auth_app.dart hosted with ❤ by GitHub
view raw
4. Form Validation
Another interesting use of a BLoC is when you need to validate a Form and:
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
18/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
validate the entry related to a TextField against some business rules;
display validation error messages depending on rules;
automate the accessibility of Widgets, based on business rules.
The example I am now going to take is a RegistrationForm which is made up of 3 TextFields (email, password, confirm password)
and 1 RaisedButton to launch the registration process.
The business rules I want to implement are:
the email needs to be a valid email address. If not a message needs to be displayed.
the password needs to be valid (must contain at least 8 characters, with 1 uppercase, 1 lowercase, 1 figure and 1 special
character). If invalid a message needs to be displayed.
the retype password needs to meet the same validation rule AND be the same as the password. If not the same, a
message needs to be displayed.
the register button may only be active when ALL rules are valid.
4.1. The RegistrationFormBloc
This BLoC is responsible for handling the validation business rules, as expressed here before.
Here is its source code:
1
class RegistrationFormBloc extends Object with EmailValidator, PasswordValidator implements BlocBase {
2
3
final BehaviorSubject<String> _emailController = BehaviorSubject<String>();
4
final BehaviorSubject<String> _passwordController = BehaviorSubject<String>();
5
final BehaviorSubject<String> _passwordConfirmController = BehaviorSubject<String>();
6
7
//
8
//
9
//
Inputs
10
Function(String) get onEmailChanged => _emailController.sink.add;
11
Function(String) get onPasswordChanged => _passwordController.sink.add;
12
Function(String) get onRetypePasswordChanged => _passwordConfirmController.sink.add;
13
14
//
15
// Validators
16
//
17
Stream<String> get email => _emailController.stream.transform(validateEmail);
18
Stream<String> get password => _passwordController.stream.transform(validatePassword);
19
Stream<String> get confirmPassword => _passwordConfirmController.stream.transform(validatePassword)
20
.doOnData((String c){
21
// If the password is accepted (after validation of the rules)
22
// we need to ensure both password and retyped password match
23
if (0 != _passwordController.value.compareTo(c)){
24
// If they do not match, add an error
25
_passwordConfirmController.addError("No Match");
26
}
27
});
28
29
//
30
// Registration button
31
Stream<bool> get registerValid => Observable.combineLatest3(
32
email,
33
password,
34
confirmPassword,
35
(e, p, c) => true
36
);
37
38
@override
39
void dispose() {
40
_emailController?.close();
41
_passwordController?.close();
42
_passwordConfirmController?.close();
43
44
}
}
bloc_reg_form_bloc.dart hosted with ❤ by GitHub
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
view raw
19/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Let me explain this in details…
We first initialize 3 BehaviorSubject to handle the Streams of each TextField of the form.
We expose 3 Function(String) which will be used to accept inputs from the TextFields.
We expose 3 Stream<String> which will be used by the TextField to display an potential error message, resulting from their
respective validation
We expose 1 Stream<bool> which will be used by the RaisedButton to enable/disable it based on the whole validation
result.
Okay, it is now time to dive into deeper details…
As you may have noticed, the signature of this class is a bit special. Let’s review it.
class RegistrationFormBloc extends Object
with EmailValidator, PasswordValidator
implements BlocBase {
...
}
The with keyword means that this class is using MIXINS ( = “a way of reusing some class code inside another class") and, to be
able to use the with keyword, the class needs to extends the Object class. These mixins contain the code which validates the
email and password, respectively.
For more details on Mixins I recommend you to read this great article from Romain Rastel.
4.1.1. Validator Mixins
I will only explain the EmailValidator since the PasswordValidator is very similar.
First, the code:
1
const String _kEmailRule = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$";
2
3
class EmailValidator {
4
final StreamTransformer<String,String> validateEmail =
5
StreamTransformer<String,String>.fromHandlers(handleData: (email, sink){
6
final RegExp emailExp = new RegExp(_kEmailRule);
7
8
if (!emailExp.hasMatch(email) || email.isEmpty){
9
sink.addError('Entre a valid email');
10
} else {
11
sink.add(email);
12
}
13
});
14
}
bloc_email_validator.dart hosted with ❤ by GitHub
view raw
This class exposes a final function ("validateEmail") which is a StreamTransformer.
Reminder
A StreamTransformer is invoked as follows: stream.transform(StreamTransformer).
The StreamTransformer takes its input from the Stream that refers to it via the transform method. It then processes this input,
and reinjects the transformed input into initial Stream.
In this code, the processing of the input consists in checking it against a regular expression. If the input matches the regular
expression, we simply reinject the input into the stream, otherwise, we inject an error message into the stream.
4.1.2. Why using a stream.transform()?
As said here before, if the validation is successful, the StreamTransformer reinjects the input into the Stream. Why is it useful?
Here comes the explanation related to the Observable.combineLatest3()… This method will NOT emit any value until all Streams
it refers to, have emitted at least one value.
Let’s have a look at the following picture to illustrate what we want to achieve.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
20/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
if the user enters an email and the latter is validated, it will be emitted by the email stream which will be one input of the
Observable.combineLatest3();
if the email is not valid, an error will be added to the stream (and no value will go out the stream);
the same applies to both password and retype password;
when all these 3 validations will be successful (meaning that all these 3 streams will emit a value), the
Observable.combineLatest3() will emit in turn a true thanks to the “(e, p, c) => true” (see line #35).
4.1.2. Validation of the 2 passwords
I saw many questions related to this comparison on Internet. Several solutions exist, let me explain 2 of them.
4.1.2.1. Basic solution - no error message
A first solution could be the following one:
1
Stream<bool> get registerValid => Observable.combineLatest3(
2
email,
3
password,
4
confirmPassword,
5
(e, p, c) => (0 == p.compareTo(c))
6
);
bloc_password_valid_1.dart hosted with ❤ by GitHub
view raw
This solution simply compares the 2 passwords as soon as they both have been validated and if they match, emits a value (=
true).
As we will soon see, the Register button accessibility will depend on this registerValid stream.
If both passwords do not match, no value is emitted by that stream and the Register button remains inactive but the user will not
receive any error message to help him understand why.
4.1.2.2. Solution with error message
Another solution consists in extending the processing of the confirmPassword stream as follows:
1
2
Stream<String> get confirmPassword => _passwordConfirmController.stream.transform(validatePassword)
.doOnData((String c){
3
// If the password is accepted (after validation of the rules)
4
// we need to ensure both password and retyped password match
5
if (0 != _passwordController.value.compareTo(c)){
6
// If they do not match, add an error
7
_passwordConfirmController.addError("No Match");
8
}
9
});
bloc_password_valid_2.dart hosted with ❤ by GitHub
view raw
Once the retype password has been validated, it is emitted by the Stream and, using the doOnData, we can directly get this
emitted value and compare it with the value of the password stream. If both do not match, we have now the possibility to send
an error message.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
21/37
11/04/2022, 13:20
4.2. The RegistrationForm
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Let’s now have a look at the RegistrationForm before explaining it:
1
class RegistrationForm extends StatefulWidget {
2
@override
3
_RegistrationFormState createState() => _RegistrationFormState();
4
}
5
6
7
class _RegistrationFormState extends State<RegistrationForm> {
RegistrationFormBloc _registrationFormBloc;
8
9
10
@override
void initState() {
11
super.initState();
12
_registrationFormBloc = RegistrationFormBloc();
13
}
14
15
@override
16
void dispose() {
17
_registrationFormBloc?.dispose();
18
super.dispose();
19
}
20
21
@override
22
Widget build(BuildContext context) {
23
24
25
26
return Form(
child: Column(
children: <Widget>[
StreamBuilder<String>(
27
stream: _registrationFormBloc.email,
28
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
29
return TextField(
30
decoration: InputDecoration(
31
labelText: 'email',
32
errorText: snapshot.error,
33
),
34
onChanged: _registrationFormBloc.onEmailChanged,
35
keyboardType: TextInputType.emailAddress,
36
37
38
);
}),
StreamBuilder<String>(
39
stream: _registrationFormBloc.password,
40
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
41
return TextField(
42
decoration: InputDecoration(
43
labelText: 'password',
44
errorText: snapshot.error,
45
),
46
obscureText: false,
47
onChanged: _registrationFormBloc.onPasswordChanged,
48
49
50
);
}),
StreamBuilder<String>(
51
stream: _registrationFormBloc.confirmPassword,
52
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
53
return TextField(
54
decoration: InputDecoration(
55
labelText: 'retype password',
56
errorText: snapshot.error,
57
),
58
obscureText: false,
59
onChanged: _registrationFormBloc.onRetypePasswordChanged,
60
61
62
);
}),
StreamBuilder<bool>(
63
stream: _registrationFormBloc.registerValid,
64
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
22/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
65
return RaisedButton(
66
child: Text('Register'),
67
onPressed: (snapshot.hasData && snapshot.data == true)
68
? () {
69
// launch the registration process
70
}
71
: null,
72
);
73
}),
74
],
75
),
76
);
77
78
}
}
bloc_reg_form.dart hosted with ❤ by GitHub
view raw
Explanation:
As the RegisterFormBloc is only meant to be used by this form, it is suitable to initialize it here.
Each TextField are wrapped into a StreamBuilder<String> to be able to respond to any outcome of the validation
process (see errorText: snapshot.error)
Each time a modification is applied to the content of a TextField, we send the input to the BLoC for validation via the
onChanged: _registrationFormBloc.onEmailChanged (case of email input)
For the RegisterButton, the latter is also wrapped in a StreamBuilder<bool>.
If a value is emitted by the _registrationFormBloc.registerValid, the onPressed method will do something
If no value is emitted, the onPressed method will be assigned to null, which will deactivate the button.
That’s it! No business rule is visible to the Form, which means that the rules could be changed without having to apply any
modification to the form, which is excellent !
5. Part Of
Sometimes, it is interesting for a Widget to know whether it is part of a set to drive its behavior.
For this last use-case of this article, I will consider the following scenario:
an application deals with items;
a user could select items to put in his shopping basket;
an item can be put in the shopping basket, only once;
an item, present in the shopping basket, could be removed from the shopping basket;
once removed, it is possible to put it back.
For this example, each item will display one button which will depend on the presence of the item in the shopping basket. If not
part of the shopping basket, the button will allow the user to add it to the basket. If part of the shopping basket, the button will
allow the user to remove it from the basket.
To better illustrate the “Part of” pattern, I will consider the following architecture:
a Shopping Page will display the list of all possible items;
each Item in the Shopping Page will display one button to add the item to the shopping basket or to remove it, depending
on its presence in the basket;
if an item in the Shopping Page is added to the basket, its button will automatically update to allow the user to remove it
from the basket (and vice versa) without having to rebuild the Shopping Page
another page, Shopping Basket, will list all items present in the basket;
it will be possible to remove any item from the basket, from this page.
Side note
The name Part Of is a personal name I give. It is not an official name.
5.1. ShoppingBloc
As you can now imagine, we need to consider a BLoC dedicated to the handling of the list of all possible items, and the ones part
of the Shopping Basket.
This BLoC could look like the following:
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
23/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
1
class ShoppingBloc implements BlocBase {
2
// List of all items, part of the shopping basket
3
Set<ShoppingItem> _shoppingBasket = Set<ShoppingItem>();
4
5
// Stream to list of all possible items
6
BehaviorSubject<List<ShoppingItem>> _itemsController = BehaviorSubject<List<ShoppingItem>>();
7
Stream<List<ShoppingItem>> get items => _itemsController;
8
9
// Stream to list the items part of the shopping basket
10
BehaviorSubject<List<ShoppingItem>> _shoppingBasketController = BehaviorSubject<List<ShoppingItem>>(seedValue: <Shopping
11
Stream<List<ShoppingItem>> get shoppingBasket => _shoppingBasketController;
12
13
@override
14
void dispose() {
15
_itemsController?.close();
16
_shoppingBasketController?.close();
17
}
18
19
// Constructor
20
ShoppingBloc() {
21
_loadShoppingItems();
22
}
23
24
void addToShoppingBasket(ShoppingItem item){
25
_shoppingBasket.add(item);
26
_postActionOnBasket();
27
}
28
29
void removeFromShoppingBasket(ShoppingItem item){
30
_shoppingBasket.remove(item);
31
_postActionOnBasket();
32
}
33
34
void _postActionOnBasket(){
35
// Feed the shopping basket stream with the new content
36
_shoppingBasketController.sink.add(_shoppingBasket.toList());
37
38
// any additional processing such as
39
// computation of the total price of the basket
40
// number of items, part of the basket...
41
}
42
43
//
44
// Generates a series of Shopping Items
45
// Normally this should come from a call to the server
46
// but for this sample, we simply simulate
47
//
48
void _loadShoppingItems() {
49
_itemsController.sink.add(List<ShoppingItem>.generate(50, (int index) {
50
return ShoppingItem(
51
id: index,
52
title: "Item $index",
53
price: ((Random().nextDouble() * 40.0 + 10.0) * 100.0).roundToDouble() /
54
100.0,
55
color: Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
56
.withOpacity(1.0),
57
);
58
}));
59
60
}
}
bloc_shopping_bloc.dart hosted with ❤ by GitHub
view raw
The only method which might require an explication is the _postActionOnBasket() method. Each time an item is added to or
removed from the basket, we need to “refresh” the content of the _shoppingBasketController Stream so that all Widgets which
are listening to changes to this Stream will be notified and be able to refresh/rebuild.
5.2. ShoppingPage
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
24/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
This page is very simple and only displays all the items.
1
class ShoppingPage extends StatelessWidget {
2
@override
3
Widget build(BuildContext context) {
4
ShoppingBloc bloc = BlocProvider.of<ShoppingBloc>(context);
5
6
return SafeArea(
7
child: Scaffold(
8
appBar: AppBar(
9
title: Text('Shopping Page'),
10
actions: <Widget>[
11
ShoppingBasket(),
12
],
13
),
14
body: Container(
15
child: StreamBuilder<List<ShoppingItem>>(
16
stream: bloc.items,
17
builder: (BuildContext context,
18
AsyncSnapshot<List<ShoppingItem>> snapshot) {
19
if (!snapshot.hasData) {
20
return Container();
21
}
22
return GridView.builder(
23
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
24
crossAxisCount: 3,
25
childAspectRatio: 1.0,
26
),
27
itemCount: snapshot.data.length,
28
itemBuilder: (BuildContext context, int index) {
29
return ShoppingItemWidget(
30
shoppingItem: snapshot.data[index],
31
);
32
},
33
);
34
},
35
),
36
),
37
));
38
39
}
}
bloc_shopping_page.dart hosted with ❤ by GitHub
view raw
Explanation:
the AppBar displays a button that:
displays the number of items, present in the basket
redirects the user to the ShoppingBasket page when clicked
the list of items is built using a GridView, wrapped in a StreamBuilder<List<ShoppingItem>>
each item corresponds to a ShoppingItemWidget
5.3. ShoppingBasketPage
This page is very similar to the ShoppingPage except that the StreamBuilder is now listening to variations of the
_shoppingBasket stream, exposed by the ShoppingBloc.
5.4. ShoppingItemWidget and ShoppingItemBloc
The Part Of pattern relies on the combination of these 2 elements:
the ShoppingItemWidget is responsible for:
displaying the item and
the button to add or remove the item to/from the shopping basket
the ShoppingItemBloc is responsible for telling the ShoppingItemWidget whether the latter is part or not of the shopping
basket.
Let’s see how they work together…
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
25/37
5.4.1. ShoppingItemBloc
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
The ShoppingItemBloc is instantiated by each ShoppingItemWidget, which gives it its “identity".
This BLoC listens to all variations of the ShoppingBasket stream and checks if the specific item identity is part of the basket.
If yes, it emits a boolean value (= true), which will be caught by the ShoppingItemWidget to know whether it is part of the basket
or not.
Here is the code of the BLoC:
1
class ShoppingItemBloc implements BlocBase {
2
// Stream to notify if the ShoppingItemWidget is part of the shopping basket
3
BehaviorSubject<bool> _isInShoppingBasketController = BehaviorSubject<bool>();
4
Stream<bool> get isInShoppingBasket => _isInShoppingBasketController;
5
6
// Stream that receives the list of all items, part of the shopping basket
7
PublishSubject<List<ShoppingItem>> _shoppingBasketController = PublishSubject<List<ShoppingItem>>();
8
Function(List<ShoppingItem>) get shoppingBasket => _shoppingBasketController.sink.add;
9
10
// Constructor with the 'identity' of the shoppingItem
11
ShoppingItemBloc(ShoppingItem shoppingItem){
12
// Each time a variation of the content of the shopping basket
13
_shoppingBasketController.stream
14
// we check if this shoppingItem is part of the shopping basket
15
.map((list) => list.any((ShoppingItem item) => item.id == shoppingItem.id))
16
// if it is part
17
.listen((isInShoppingBasket)
18
// we notify the ShoppingItemWidget
19
=> _isInShoppingBasketController.add(isInShoppingBasket));
20
}
21
22
@override
23
void dispose() {
24
_isInShoppingBasketController?.close();
25
_shoppingBasketController?.close();
26
27
}
}
bloc_shopping_item_bloc.dart hosted with ❤ by GitHub
view raw
5.4.2. ShoppingItemWidget
This Widget is responsible for:
creating an instance of the ShoppingItemBloc and passing its own identity to the BLoC
listening to any variation of the ShoppingBasket content and transferring it to the BLoC
listening to the ShoppingItemBloc to know whether it is part of the basket
displaying the corresponding button (add/remove) depending on its presence in the basket
responding to user action of the button
when the user clicks the add button, to add itself to the basket
when the user clicks the remove button, to remove itself from the basket.
Let’s see how this works (explanation is given in the code).
1
class ShoppingItemWidget extends StatefulWidget {
2
ShoppingItemWidget({
3
Key key,
4
@required this.shoppingItem,
5
}) : super(key: key);
6
7
final ShoppingItem shoppingItem;
8
9
@override
10
11
_ShoppingItemWidgetState createState() => _ShoppingItemWidgetState();
}
12
13
14
class _ShoppingItemWidgetState extends State<ShoppingItemWidget> {
StreamSubscription _subscription;
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
26/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
15
ShoppingItemBloc _bloc;
16
ShoppingBloc _shoppingBloc;
17
18
@override
19
void didChangeDependencies() {
20
super.didChangeDependencies();
21
22
// As the context should not be used in the "initState()" method,
23
// prefer using the "didChangeDependencies()" when you need
24
// to refer to the context at initialization time
25
_initBloc();
26
}
27
28
@override
29
void didUpdateWidget(ShoppingItemWidget oldWidget) {
30
super.didUpdateWidget(oldWidget);
31
32
// as Flutter might decide to reorganize the Widgets tree
33
// it is preferable to recreate the links
34
_disposeBloc();
35
_initBloc();
36
}
37
38
@override
39
void dispose() {
40
_disposeBloc();
41
super.dispose();
42
}
43
44
// This routine is reponsible for creating the links
45
void _initBloc() {
46
// Create an instance of the ShoppingItemBloc
47
_bloc = ShoppingItemBloc(widget.shoppingItem);
48
49
// Retrieve the BLoC that handles the Shopping Basket content
50
_shoppingBloc = BlocProvider.of<ShoppingBloc>(context);
51
52
// Simple pipe that transfers the content of the shopping
53
// basket to the ShoppingItemBloc
54
_subscription = _shoppingBloc.shoppingBasket.listen(_bloc.shoppingBasket);
55
}
56
57
void _disposeBloc() {
58
_subscription?.cancel();
59
_bloc?.dispose();
60
}
61
62
Widget _buildButton() {
63
return StreamBuilder<bool>(
64
stream: _bloc.isInShoppingBasket,
65
initialData: false,
66
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
67
return snapshot.data
68
? _buildRemoveFromShoppingBasket()
69
: _buildAddToShoppingBasket();
70
},
71
72
);
}
73
74
Widget _buildAddToShoppingBasket(){
75
return RaisedButton(
76
child: Text('Add...'),
77
onPressed: (){
78
_shoppingBloc.addToShoppingBasket(widget.shoppingItem);
79
},
80
81
);
}
82
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
27/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
83
Widget _buildRemoveFromShoppingBasket(){
84
return RaisedButton(
85
child: Text('Remove...'),
86
onPressed: (){
87
_shoppingBloc.removeFromShoppingBasket(widget.shoppingItem);
88
},
89
);
90
}
91
92
@override
93
Widget build(BuildContext context) {
94
return Card(
95
child: GridTile(
96
header: Center(
97
child: Text(widget.shoppingItem.title),
98
),
99
footer: Center(
100
child: Text('${widget.shoppingItem.price} €'),
101
),
102
child: Container(
103
color: widget.shoppingItem.color,
104
child: Center(
105
child: _buildButton(),
106
),
107
),
108
),
109
);
110
111
}
}
bloc_shopping_item.dart hosted with ❤ by GitHub
view raw
5.5. How does all this work?
The following diagram shows how all pieces work together.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
28/37
Conclusions
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Another long article I wish I could make a bit shorter but I think it deserved some explanations.
As I said in the introduction, I personally use these “patterns” very frequently in my developments. This makes me save a
tremendous amount of time and effort; my code is more readable and easier to debug.
In addition, it helps separating the business from the view.
There are most certainly other ways of doing this and even better ways but it simply works for me and that’s all I wanted to share
with you.
Stay tuned for new articles and, meanwhile, I wish you a happy coding.
Share:
    
ALSO ON DIDIERBOELENS
StatefulWidget
Event Loop
addPostFrameCallback
Language …
Form …
2 years ago • 1 comment
3 years ago • 5 comments
3 years ago • 19 comments
3 years ago • 32 comments
2 years ago
Flutter - How to interact
with or have interactions
between several …
Single Thread, multi
threading, synchronous et
asynchronous. Cet article …
How can I show a Dialog
the very first time I load a
page? How can I do …
How can you handle
language selection from
anywhere in the …
Correct For
using Comb
Question W
didierboelens Comment Policy
All comments are welcome. Non-relevant comments will be removed.
117 Comments
didierboelens
 Favorite 16
t Tweet
🔒 Disqus' Privacy Policy
f Share

1

Login
Sort by Best
Join the discussion…
LOG IN WITH
OR SIGN UP WITH DISQUS ?
Name
Ray Li • 3 years ago
Wow, thank you so much for writing these articles! This feels like a glimpse into a secret vault full of all the goodies. Your previous
article helped me finally understand the BLOC pattern where no other tutorial did. This article will help improve my Flutter
development so much. You've helped me so much with Flutter, more than you know!
10 △
▽ • Reply • Share ›
boeledi
Mod
> Ray Li • 3 years ago
Thank you so much for your feedback.
3△
▽ • Reply • Share ›
Michel Feinstein • 3 years ago
Wow that's a amazing article! I have some comments about it:
1 - This pattern of reacting to events and posting a State, reminds me a lot of Redux, so why no use it instead?
2 - Could you please make an in depth article about "didChangeDependencies()", "did Update Widget" and "initState()" they
seem a bit obscure and easy to use at the wrong time (or identify the right time for each one to be used).
3 - The Navigator rebuilding past pages is definitely something I didn't see it coming! Thanks for the tip!
5△
▽ • Reply • Share ›
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
29/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
JaponicaGr • 3 years ago
Oh sir, what an article!
I think i have to read this several times.. a lot of staff here.
I really appreciate your effort in a very confusing pattern(at least for me).
4△
▽ • Reply • Share ›
Filip Lindman Marko • 2 years ago
Is the side note in 2.2 still valid now that BlocProvider takes a builder?
1△
▽ • Reply • Share ›
boeledi
Mod
> Filip Lindman Marko • 2 years ago
Of course, the side note is no longer relevant after the modification of the interface. I have removed it. Thanks for spotting
it.
1△
▽ • Reply • Share ›
Filip Lindman Marko > boeledi • 2 years ago
Thank you for clarifying!
2△
▽ • Reply • Share ›
Filip Lindman Marko > boeledi • 2 years ago
I'm curious about the motivation behind this change. Is there some other reason for this except avoiding an extra
stateful widget?
Also, will we see an update to the linked git repo (https://github.com/boeledi/...
1△
▽ • Reply • Share ›
boeledi
Mod
> Filip Lindman Marko • 2 years ago
Hi Filip,
The reason why I updated the interface is the following: simply to make sure that the bloc is not reinstantiated should there be any rebuild of a StatefulWidget. The blocBuilder is only invoked ONCE and only
once (at the level of the initState of the _BlocProviderState class).
As regards the different repositories, I will review them during the week-end.
Kind Regards,
Didier
1△
▽ • Reply • Share ›
High Middle Sky • 2 years ago • edited
Thank you for your good case study.
First I'm sorry because my bad English skill.
I have a question.
In authentication using bloc, when user logout how to detect the state in other page?
For example, user already has login and is in homepage.
If user pushed the button for logout on action bar and call bloc.emitEvent(AuthenticationEventLogout()) then how to know
whether 'isAuthenticated' is false or not in homepage.
Since I used 'return BlocEventStateBuilder<authenticationevent, authenticationstate="">' on DecisionPage, I didn't use 'return
BlocEventStateBuilder<authenticationevent, authenticationstate="">' on other pages, but just called 'bloc =
BlocProvider.of<authenticationbloc>(context)'.
///// In homepgae /////
@override
void initState() {
super.initState();
bloc = BlocProvider.of<authenticationbloc>(context);
}
see more
1△
▽ • Reply • Share ›
Joe Lapp • 3 years ago
Regarding changing `of()` from O(n) to O(1), I'm glad to see the Dart/Flutter community concerned about library performance. My
background is largely middleware Java, where sometimes every clock cycle matters. I have several times reported performance
issues in Javascript open source libraries, and every time I've been shot down with something like, "It's negligible compared to
DB/file/network access and therefore a waste of our time to consider. CLOSED." Never mind that their libraries aren't always used
in client/server solutions.
1△
▽ • Reply • Share ›
Felipe Gonçalves dos Reis • 3 years ago
Great article. I'm using in my first app and I'm really happy with this patterns and all your explanation.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
30/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
I'm impressed with all this. Thank you so much.
1△
▽ • Reply • Share ›
Stevie Grows, Bud! • 3 years ago
Thanks for the article, very informative and helpful.
I have been experimenting with your authentication bloc implementation and trying to integrate Firebase authentication with it.
I am stumped as to how to persist the authentication data. I would need to request the current user from Firebase and set the
initial AuthenticationState based on whether a user is returned or not. Nothing I have tried has worked so far...
1△
▽ • Reply • Share ›
Jorge Gonçalves • 3 years ago • edited
Many, many thanks for your article! In fact, for the fantastic series of articles!
I have just stumbled in a problem that you may have a better solution.
In fact my case is not in a login form, it is an internal form in an app I am writing right now.
When using your ideas to use RxDart in form validation I had to modify the way you use the validators.
This happened because, as the validators filter the streams, all that get to Observable.combineLatest are valid values. In the
scenario when the user typed a valid email, valid passwords and is ready to press the register button but, all of a suddden, he
comes back and modifies the email to an invalid one.
Up to this point all is OK, as the register btn is greyed out. But if he continues and re-enters valid passwords then is that the
problem arises, as the register button will be activated!
This happens because the passsword streams have been already fired and valid thus they get to Observable.combinelatest, that
already has a valid email... the one before the error.
To solve this I feed the streams with all values and I brought the validators to inside the combinator in Observable.combinelatest,
and it is there also that I sink the errors when they appear.
I hope I could make my point clearly.
Please refer also to this: https://github.com/Reactive...
Please let me know if there is a better solution to this!
Thanks again for all the enlightment! I would have never got to this point without your help!!!!
1△
▽ • Reply • Share ›
Dawid Tomkalski > Jorge Gonçalves • 2 years ago • edited
@Jorge Gonçalves I spent many hours looking for a solution to the exactly the same issue, I tried many example apps
from github and they all have the same bug, I like the way I can manage forms with rx but that issue should have some
kind of solution. did you find any other solution? I wish author of that blog would help to solve that @boeledi
1△
▽ • Reply • Share ›
Jorge Gonçalves > Dawid Tomkalski • 2 years ago
No, I am currently using this solution. Working so far!
Let me know if I can help with further info or example code.
△ ▽ • Reply • Share ›
boeledi
Mod
> Dawid Tomkalski • 2 years ago • edited
Hi Dawid and @Jorge Gonçalves ,
Following this discussion, I have implemented the following solution (
Gist):
class ValidateAllStreamsHaveDataAndNoErrors {
void listen(List< Stream > streams){
_controller = StreamController<bool>.broadcast();
errors = List.generate(streams.length, (_) => true);
List.generate(streams.length, (int index) {
return streams[index].listen(
(data){
errors[index] = false;
_validate();
},
onError: (_){
see more
△ ▽ • Reply • Share ›
Ronnie Ren • 2 years ago • edited
thanks for your articles, they are very helpful.
Ab t
L i & th ti ti
h
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
t t
(diff
t t t
)
d
t
ith
d t
i t d
ith th
d
31/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
About your Login & authentication cases, you have states(different states) and events with some data associated with them, and
then your bloc accepts the events and do some calculation(business logics) to output a new state.
the logics is fine, however I am thinking if it is a little verbose since you have to create extra objects (event, state)? why not
directly use the same approach as the case you show in the Form Validation? bloc expose the stream with rx way and UI
consumes this stream, I very like this way and is very straightforward.
what do you think ? any comments are welcome.
△ ▽ • Reply • Share ›
Mero Lee • 2 years ago • edited
Thank you for your sharing very much. I have a question about the code:
abstract class BlocEventStateBase<blocevent, blocstate=""> implements BlocBase {
whether it is same as
abstract class BlocEventStateBase<t extends="" blocevent,="" s="" extends="" blocstate=""> implements BlocBase {
or it`s my misunderstanding.
△ ▽ • Reply • Share ›
Antonio José García Jiménez • 2 years ago
Thanks for your great article!
Now, after Flutter v1.12.1, ancestorInheritedElementForWidgetOfExactType is deprecated.
Could you rewrite using getElementForInheritedWidgetOfExactType?
Thanks!
△ ▽ • Reply • Share ›
Juraj Jurčo > Antonio José García Jiménez • 2 years ago
yes, the code is less confusing then. you can drop one function you use. Dart takes already takes the type from the return
type and the code is the same.
△ ▽ • Reply • Share ›
Akshay Malhotra • 2 years ago
I'm not able to understand as it's too much information for me, can you guide me where to start to learn basic with examples ?
△ ▽ • Reply • Share ›
Thai Ngo • 3 years ago
Thanks for very useful article. I already use for share with community flutter in Viet Nam.
△ ▽ • Reply • Share ›
loic ngou • 3 years ago
Nice explanation, It is similar to flutter_bloc package from felix angelov
△ ▽ • Reply • Share ›
Joe Lapp • 3 years ago • edited
I'm new to reactive programming and don't understand why it's using streams. It seems to me that if the UI rebuilds on events
that change state, the UI only cares about the final state at the time it rebuilds. Why handle each individual event (unless the UI is
doing business logic)? Why not just note a state change and inform all listeners to rebuild?
△ ▽ • Reply • Share ›
Станіслав Д • 3 years ago
Hi, what about logging? What is the best way to organize a log for events and states?
△ ▽ • Reply • Share ›
Hai Pham • 3 years ago
please tell me why, as you wrote (DecisionPage class):
Trick
As we cannot directly redirect to another page from the builder, we use the WidgetsBinding.instance.addPostFrameCallback()
method to request Flutter to execute a method as soon as the rendering is complete
--------------I try update your code as below:
void _redirectToPage(BuildContext context, Widget page){
//your code
// WidgetsBinding.instance.addPostFrameCallback((_){
// MaterialPageRoute newRoute = MaterialPageRoute(
// builder: (BuildContext context) => page
// );
//
// Navigator.of(context).pushAndRemoveUntil(newRoute, ModalRoute.withName('/decision'));
// });
//new code
M t i lP
R t
R t
M t i lP
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
R
t (
32/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
MaterialPageRoute newRoute = MaterialPageRoute(
builder: (BuildContext context) => page
);
Navigator.of(context).pushAndRemoveUntil(newRoute, ModalRoute.withName('/decision'));
}
// there is no compile error
so why is this - As we cannot directly redirect to another page from the builder?
Thanks in advance!!!!
△ ▽ • Reply • Share ›
boeledi
Mod
> Hai Pham • 3 years ago
Hi,
Simply because in the "build" method, you cannot directly redirect to another page.
This is logical. In the "build" method, you are supposed to build the content of the Widget and, if you are using
"Navigator.of(context)....", the content you are building is useless... Therefore, Flutter prevents you from being able to do
such things.
This is why I am using the "WidgetsBinding.instance.addPostFrameCallback()" to ask to be called 'one the build is done',
so that I can directly redirect (right after).
Hope this answers your question.
Regards,
Didier
1△
▽ • Reply • Share ›
Quang Le • 3 years ago
Hi Didier, thanks for another great article! I have a question about the BlocProvider: you recommend to use a Stateful Widget to
instantiate the BloC when using the BlocProvider in a sub-tree to prevent the Bloc from being instantiated every time `build` runs.
But you don't do it with when the BlocProvider is the parent of the MaterialApp widget. In my experience, the `build`method of
MaterialApp can also be called multiple times, thus re-instantiatingthe BloC, so I wonder why you don't also make your
Application widget Stateful.
△ ▽ • Reply • Share ›
boeledi
Mod
> Quang Le • 3 years ago
Hi Sir,
Sorry for this very late answer but I was off abroad.
In fact, you are absolutely right and I all my applications, the Application Widget is always a StatefulWidget which looks
like the following, ONLY because I need to fully be in control of everything:
class Application extends StatefulWidget {
@override
ApplicationState createState() => ApplicationState();
}
class ApplicationState extends State<application> {
MyBloc myBloc;
@override
void initState() {
super initState();
see more
△ ▽ • Reply • Share ›
Thai Ngo • 3 years ago
Thank your great articles!, I have a question. When model of ui and model in Bloc is different. How we handle that. In uncle bob
clean architecture, have model for each part (presenter, domain and data) and easy convert between them.
△ ▽ • Reply • Share ›
boeledi
Mod
> Thai Ngo • 3 years ago
Hi,
In fact, using the notion of BLoCs, does not systematically mean that you need to have a 1-to-1 relationship between the
models. The BLoC is mainly meant to hold the business logic aside so that it remains outside the view (and could be
changed without too much impact).
So far, I still haven't had any 1-to-1 relationship between view, model and bloc...
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
33/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
So far, I still haven t had any 1 to 1 relationship between view, model and bloc...
Regards,
1△
▽ • Reply • Share ›
Thai Ngo > boeledi • 3 years ago
Thanks so much, but I have another question. When we have simple logic ui must handle. Will we us bloc?. In
uncle bob clean architecture, we handle this in Presenter.
△ ▽ • Reply • Share ›
boeledi
Mod
> Thai Ngo • 3 years ago
Hi,
If you think a bit further... don't you find that a BLoC could also be considered as some "P" in a MVP
architecture in some extend ? :)
In fact your View simply react to external event (Pointer events, device rotation, ...) to transmit this
information to the BLoC, which treats the information and, if necessary, requests the View to refresh (via
Streams or Notifiers). Looks to me very close to a "P" (but I don't like putting labels on things).
Regards,
Didier
△ ▽ • Reply • Share ›
loic ngou • 3 years ago
wow , thanks for article sir , i have read it several , and now i'm trying to add favourites items features following your logic , i
added in shopping_bloc a controller to manage favorites list , and it work like a charm, but i also wanted to build add to favorites/
remote from favorites button , so how can i listen for favoriteItems from ShoppingItemBloc? there is my ShoppingItemBloc.
thanks
// Stream to notify if the ProductItemWidget is part of the favorites items
BehaviorSubject<bool> _isInFavoritesController = BehaviorSubject<bool>();
Stream<bool> get isInFavorites=> _isInFavoritesController;
// Stream that receives the list of favorite items
PublishSubject<list<product>> _favoriteItemsController = PublishSubject<list<product>>();
Function(List<product>) get favoriteItems=> _favoriteItemsController.sink.add;
// Constructor with the 'identity' of the product
see more
△ ▽ • Reply • Share ›
Roshan madusanka • 3 years ago
Hello sir,
Im trying to implement Bloc with your guidance , but the issue is value not updated.
ok . im trying to implement common number increment sample application with bloc
but value is not getting updated.
i have implemented your provider class as yours.
here is my main widget code
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BlocProvider(bloc: CounterBloc(initialCount: 1),child: Counter(),),
floatingActionButton: FloatingActionButton(
onPressed: _counterBloc.increment,
tooltip: 'Increment',
hild I
(I
dd)
see more
△ ▽ • Reply • Share ›
Roshan madusanka > Roshan madusanka • 3 years ago
Ok , i figure out the problem inside my main widget my call to increment method is from new instance of the bloc , not
from the registered class with provider.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
34/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
Thank you for the great article.
thanks again.
△ ▽ • Reply • Share ›
franco • 3 years ago
Didier, I have a question: why do you use a BehaviorSubject in the definition of the _stateController?
△ ▽ • Reply • Share ›
boeledi
Mod
> franco • 3 years ago
Hi Franco
BehaviorSubject is very convenient as it is directly a broadcast stream but also because if another listener is interested in
obtaining the very last State, it has it...
Regards
Didier
△ ▽ • Reply • Share ›
franco • 3 years ago • edited
Thanks for the great articles, I also really enjoyed your youtube Paris presentation about BLoC. I think it really visually
complements your articles.
I have a question though, in your _BlocProviderInherited<t> class, why you are returning false in the updateShouldNotify
function? This way you are not getting your consumer widgets automatically rebuild after some possible changes in the state of
the Bloc.
△ ▽ • Reply • Share ›
Andres CR • 3 years ago
Hey Didier, this was great, I started to refactor my app before its too late, I do have one problem and Ive been trying to work on it
for a while with no luck, once the decisionPage redirects me to the HomePage, the homePage has the backArrow button in the
app bar and even if I remove it, with the back hardware button I reach an empty black screen instead of exiting the app, do you
have any insights? thank you!
△ ▽ • Reply • Share ›
boeledi
Mod
> Andres CR • 3 years ago
Hi Andres,
There is a simple solution to this, the "WillPopScope" widget.
Sort example:
Future<bool> _onWillPopScope() async {
// popup whatever business logic you want here
return false; // If you do not want any back to be considered (such as the Android back button, e.g.)
}
@override
Widget build(BuildContext context){
return WillPopScope(
onWillPop: _onWillPopScope,
child: SafeArea(
child: Scaffold(
...
),
),
);
}
Hope this helps.
Didier
△ ▽ • Reply • Share ›
Andres CR > Andres CR • 3 years ago
I ended up dropping the decisionPage altogether, and using a streambuilder that listens to onAuthStateChanged to build a
MaterialApp with different home pages
△ ▽ • Reply • Share ›
boeledi
Mod
> Andres CR • 3 years ago
Hi Andres,
I personally do something very similar, lately. However, I still have a kind of DecisionPage (I now call
'C t ll P
') hi h li t
t
t t h
t d id
h
t
di t Thi i
i
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
t
35/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
'ControllerPage'), which listens to some state changes to decide where to redirect. This is so convenient.
The only thing I add to the Decision/Controller page is a check whether we are doing a hot reload.
Here below an extract of a ControllerPage, that works in production now.
UserProfileState oldUserProfileState;
bool _isHotReload = false;
@override
void reassemble() {
super.reassemble();
_isHotReload = true;
}
see more
△ ▽ • Reply • Share ›
André Vendramini • 3 years ago
Great article. Don't stop writing!
△ ▽ • Reply • Share ›
SilenceZhou • 3 years ago
:
I am running a project error
Compiler message:
lib/blocs/shopping/shopping_bloc.dart:16:77: Error: No named parameter with the name 'seedValue'.
BehaviorSubject<int> _shoppingBasketSizeController = BehaviorSubject<int>(seedValue: 0);
^^^^^^^^^
file:///Users/zhouyun/.pub-cache/hosted/pub.flutter-io.cn/rxdart-0.... Context: Found this candidate, but the arguments don't
match.
factory BehaviorSubject({
^
lib/blocs/shopping/shopping_bloc.dart:19:84: Error: No named parameter with the name 'seedValue'.
BehaviorSubject<double> _shoppingBasketPriceController = BehaviorSubject<double>(seedValue: 0.0);
^^^^^^^^^
file:///Users/zhouyun/.pub-cache/hosted/pub.flutter-io.cn/rxdart-0.... Context: Found this candidate, but the arguments don't
match.
factory BehaviorSubject({
^
lib/blocs/shopping/shopping_bloc.dart:23:103: Error: No named parameter with the name 'seedValue'.
BehaviorSubject<list<shoppingitem>> shoppingBasketController = BehaviorSubject<list<shoppingitem>>(seedValue:
see more
△ ▽ • Reply • Share ›
boeledi
Mod
> SilenceZhou • 3 years ago
Hi,
This error comes from the fact that RxDart package, as of version 0.21, changed the signature of the BehaviorSubject.
Rather than initializing with a seedValue argument, you now need to do the following way:
BehaviorSubjet<t> _controller = BehaviorSubject<t>.seeded(T value);
I think the author of that package should had marked the "old" intialization way as "obsolete" but they did not do it.. :(
As regard your second question, please be my guest.
Could you please send me the link to your translation?
Kind regards,
Didier
△ ▽ • Reply • Share ›
SilenceZhou > boeledi • 3 years ago
Hi,
Thank you for your reply, I translated your blog in Chinese, the translation link is 'https://silencezhou.github.... .
1△
▽ • Reply • Share ›
semut • 3 years ago • edited
Hi Sir,
Your tutorial very useful helped me a lot as a beginner just learnig dart and flutter.
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
36/37
11/04/2022, 13:20
Flutter - Reactive Programming - Streams - BLoC - Practical Use Cases
I have a question as a newbie, What do you think if in your shopping_item_bloc.dart code, i would use the factory pattern
instead?
Thank You for your attention.
△ ▽ • Reply • Share ›
Felipe K • 3 years ago
Hello! Didier, I'm a big fan, I read a lot of your articles and learned many things.
I wonder if you could help. I even asked a question in stackoverflow but I do not know how to solve yet, it's about item 4.
Basically I'd like to know how to reset the fields after pressing the submit button on this form structure that you proposed.
StreamTransformers end up validating the fields and marking as an error. Thank you!
https://stackoverflow.com/q...
△ ▽ • Reply • Share ›
didierboelens.com assumes no responsibility or liability for any errors or omissions in the content of this site. The information
contained in this site is provided on an ‘as is’ basis with no guarantees of completeness, accuracy, usefulness or timeliness.
  
Quick Links
About
Blog / Articles
Contact
Services
Contact Info
mail@didierboelens.com
Copyright © 2020 - Didier Boelens
https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/
37/37
Download