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