preface
A lot of Inspiration for Flutter comes from React. The idea behind Flutter is to separate data from views and render them using data maps. Therefore, in Flutter, its widgets are immutable, and its dynamic parts are all placed in states.
In the previous article, we have introduced the application of scoped Model and Redux state management schemes in FLUTTER. They all seem ok, but they’re still a fly in the ointment. Today I’m going to introduce a new solution proposed by Google called BLoC.
Before we begin, I hope you have read and understood the stream, and the rest of the article is based on that. Dart: What is a Stream if you haven’t already read dart: What is a Stream.
BLoC
Why do YOU need state management
We are always looking for powerful state management. You may not have thought about it, but Flutter itself already provides us with state management, and you use it a lot.
Yes, it was Stateful Widget. When we approach flutter, the first thing we need to understand is that some widgets are stateful and some are stateless. Stateless Widgets and Stateful Widgets.
Stateful Widget was designed to contain immutable data and the State that was created over stateful Widgets. All of its member variables should be final, and when the state changes, we need to tell the view to redraw. This process is called setState.
So this looks pretty good, we just setState when we change the state. When we first build the application, it may be easy, and we may not need state management at this point.
But as you add functionality, your application will have dozens or even hundreds of states. This is what your application should look like.
Can I refresh the page without using setState? How do I share state across multiple pages? We want a more powerful way to manage our state.
What is the BLoC
BLoC is an approach to building applications using Reactive programming, a completely asynchronous world of flows.
- Wrap stateful parts with StreamBuilder, which listens for a stream
- This stream is coming from BLoC
- The data in the stateful widget comes from the stream being listened on.
- The user interaction gesture was detected and an event was generated. Like when you press a button.
- Call the function bloc to handle this event
- After processing in bloc, the latest data will be added into the sink of the stream
- The StreamBuilder listens for the new data, generates a new snapshot, and calls the Build method again
- The Widget is rebuilt
BLoC allows us to separate business logic perfectly! No more worrying about when you need to refresh the screen, leave it all to StreamBuilder and BLoC! Say goodbye to StatefulWidget!!
BLoC stands for Business Logic Component. It was designed by Two engineers from Google, Paolo Soares and Cong Hui, and was first demonstrated during the 2018 DartConf (January 23-24, 2018). Click to watch the Youtube video.
Lets do it!
Let’s use CountApp as a simple example. A brief introduction to the use of BLoC. The complete code for the project has been uploaded to Github.
BLoC is an app that uses BLoC to share status information across different pages. Both pages rely on a number that increases with the number of times we press the button.
Step 1: Create BLoC
Our requirement here is very simple, just output a number, and then have a method to increment the number by one. So we need to create a stream that can pass data of type int.
import 'dart:async';
class CountBLoC {
int _count;
StreamController<int> _countController;
CountBLoC() {
_count = 0;
_countController = StreamController<int> (); } Stream<int> getvalue => _countController.stream; increment() { _countController.sink.add(++_count); } dispose() { _countController.close(); }}Copy the code
Why use the private variable “_”
An application needs a lot of developers, and your code might be seen months later by another developer, and if your variables aren’t protected, it might be countController.sink.add(++_count), Instead of calling the increment method.
Although the effects of the two methods are exactly the same, the second method will make our business logic scattered into other codes, which improves the degree of code coupling and is very bad for the maintenance and reading of the code. Therefore, in order to completely separate our business logic from BLoC, please be sure to use private variables.
Step 2: Create BLoC instance
There are three ways to create bloc
- Global singleton creation
- Local create
- scoped
Since we need to access the same Bloc in both screens, we can only choose global singleton mode or Scoped mode.
Global singleton pattern
Global singleton We just need to create a Bloc instance in the file of the Bloc class. I don’t recommend it though, because we should release the bloc when we don’t need it.
But to keep my explanation simple, I will introduce it based on the global singleton pattern.
Scoped mode
Create a Bloc Provider class, where we need to implement the InheritWidget with the of method and make updateShouldNotify return true.
class BlocProvider extends InheritedWidget {
CountBLoC bLoC = CountBLoC();
BlocProvider({Key key, Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(_) => true;
static CountBLoC of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bLoC;
}
Copy the code
Tip: Here updateShouldNotify requires passing in an InheritedWidget oldWidget, but we force it to return true, so pass an “_” placeholder.
Step 3: Use the StreamBuilder in your page
In this example, the first page displays only text + numbers.
StreamBuilder<int>(
stream: bloc.value,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text(
'You hit me: ${snapshot.data} times',
style: Theme.of(context).textTheme.display1,
);
})
Copy the code
- The stream parameter in StreamBuilder represents the stream that the StreamBuilder is listening for. Here we are listening for the value of countBloc (which is a stream).
- InitData represents the initial value, because when the control is first rendered, there is no interaction with the user and no events flow out of the stream. So you need to give the first render an initial value.
- The Builder function takes a location argument, BuildContext, and a snapshot. Snapshot is a snapshot of the output data of this flow. Snapshot. data allows you to access the data in a snapshot. You can also use snapshot.hasError to determine if there is an exception and get the exception from snapshot.error.
- The Builder in StreamBuilder is an AsyncWidgetBuilder that can asynchronously build widgets that will be rebuilt when data is detected coming out of the stream.
Call INCREMENT on the second page
floatingActionButton: FloatingActionButton(
onPressed: ()=> bloc.increment(),
child: Icon(Icons.add),
)
Copy the code
Since there is no widget refactoring involved here, we just need to call bloc’s function.
Handling broadcast streams
Once we’ve built the UI, running the program will discover this strange thing.
flutter: Bad state: Stream has already been listened to.
Copy the code
This is caused by the stream being listened on repeatedly. This number needs to be displayed on both pages, so two StreamBuilders are used. Streambuilders listen on the same stream, so the stream is listened on twice.
Remember when we were in the Dart | what is said in the Stream of two kinds of flow? Yes, when we create StreamController, the default is to create a single subscription stream. So we need to change the stream to a broadcast stream.
_countController = StreamController.broadcast<int> ();Copy the code
Just call the broadcast method when you create the StreamController.
Let’s see what happens
Did you find it
Q&A
Why is the counter displayed as 0 when I enter UnderPage the second time
This is due to the fact that when we first pop UnderPage, the page has already been destroyed. When we push in again, the StreamBuilder cannot hear the last event (it has already streamed) and only displays initiaData. When clicked again, the correct number is added to the stream and the StreamController listens to it, so the correct data is displayed again.
Can this problem be solved?
The answer is yes, use rxDart! Rxdart greatly enhances the functionality of streams, which will be addressed in a subsequent RxDART article.
How should BLoC be organized in a large application
Large applications require multiple Blocs. A good pattern is to use one top-level component for each screen and one for each sufficiently complex widget. But too many blocs can be troublesome. In addition, if you have hundreds of observable quantities (streams) in your application, this can negatively impact performance. In other words: Don’t overdesign your application.
– Filip Hracek
A more sophisticated app
A more sophisticated sample of BLoC
To learn more
Here are some excellent articles for you to refer to more
- Flutter responsive programming: Streams and BLoC
- Build Reactive Mobile apps in Flutter — Companion article
- Technical Debt and Streams/BLoC (The Boring Flutter Development Show, Ep. 4)
- Using the BloC Pattern to Build Reactive Applications with Streams in Dart’s Flutter Framework
Write in the last
This code is already used by uploading Github:github.com/OpenFlutter…
Bloc is an excellent state management approach that helps us build complex large-scale applications. But he’s not perfect (at least not yet). It does a good job of handling a large number of asynchronous events and separating business logic, but it has some drawbacks in shared state.
Redux has been tried with Bloc in an attempt to find a breakthrough. Here’s a library written just for it: Rebloc. Interested friends can find out by themselves.
If you have any good ideas or questions about state management using Bloc, please leave them in the comments section below and on my email at [email protected], and I will contact you within 24 hours!
The next article will introduce the practice of RxDart, the best library in Reactive Programming, on BLoC.