complaints
Home can not tolerate the soul, foreign country can not tolerate the body! If I can safe, who may displaced.Copy the code
The body of the
In Dart, there is no concept of multithreading. All asynchronous operations are performed in a single thread, and there is no lag because of the Event Loop.
As shown in the figure below, there are two events during the execution of the program
If Micortask Queue is empty, EventQueue will be executed. If EventQueue is empty, the program will end. In fact, the event loop will continue from the start.
During program execution, if there is an asynchronous operation, the operation is added to the queue, and when the queue is not empty, events are continually fetched from the queue for execution
Microtask Queue
A top-level queue that executes tasks as long as the queue is not empty,
scheduleMicrotask(() {
print("Hello Flutter");
});
Copy the code
Future.microtask() // The function above is also called internally
Copy the code
However, it is important to note that in normal practice, we do not manually add events to the queue, which is normally handled by Dart itself.
Event Queue
A normal event Queue, one level lower than a Microtask Queue, executes tasks in the Microtask Queue only when there are no tasks in the Queue
Code that requires asynchronous operations is executed in the EventQueue, for example:
Future((){
print('This is going to be executed in EventQueu');
})
Future.delayed(Duration(seconds: 1), () {
print('This is going to be executed in EventQueu');
});
Copy the code
Code that executes directly
Future.sync(() => print('Hello'));
Future.value(() => print('world'));
xxx.then()
Copy the code
Future
The code inside a Flutter is handed over to EventQueue for execution. The states of a Future can be divided into three categories:
Future(() {
print('Unfinished state');
})
.then((value) => print('Completed state'))
.catchError((value) => print('Abnormal state'));
Copy the code
Most of the asynchronous operations in our program revolve around these three states.
Future common functions
-
Future.error()
Future(() { return Future.error(Exception()); }).then((value) => print('Completed state')).catchError((value) => print('Abnormal state')); Copy the code
Create a Future that ends with an exception, and the code above will eventually be executed in the catchError.
-
Future.whenComplete()
Similar to finnaly after try catch, success or failure, will end up here
-
Future.them chain call
// Return values can be followed in them, which will be returned in the next chained then call getNetData().then((value) { // Support succeeded here print(value); return "data1"; }).then((value) { print(value); return "data2"; }).then((value) { print(value); }).catchError((error) { // Execution failed up to this point print(error); }).whenComplete(() => print("Complete")); Copy the code
-
Future.wait()
If you want to wait until multiple asynchronous tasks have finished to do something, you can use future.wait
Future.wait([getNetData(), getNetData(), getNetData()]).then((value) { value.forEach((element) { print(element); }); }); Copy the code
-
Future.delayed()
Delay specifies the time after execution
Future.delayed(Duration(seconds: 3), () { return "Message in three seconds."; }).then((value) { print(value); }); Copy the code
-
Async and await
Async: Used to indicate that a function is asynchronous. The defined function returns a Future object. Callbacks can be added using then
“Await” : a Future means to await the completion of an asynchronous task. After the async task is completed, the async task will go further. “await” must appear inside async
void main() { print("start ----------->"); getNetData().then((value) => print(value)); print("end -------------->"); } // Async encapsulates the returned result as a Future type getNetData() async { var result1 = await Future.delayed(Duration(seconds: 3), () { return "Network Data 1"; }); var result2 = await Future.delayed(Duration(seconds: 3), () { return "Network Data 2"; }); return result1 + "-- -- -- -- --" + result2; } Copy the code
FutureBuilder
Listen on a Future, setState the state of the Future
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
returnDemoWidget() }); }}Copy the code
structure
-
Future: Accepts a future. When the value of the future changes, the following build function is automatically called.
-
InitialData: An initial value that can be used temporarily if the future is not completed. This value is placed in the AsyncSnapshot data and can be used when the future is not completed. In case of a future error, this value is deleted from the data by AsyncSnapshot
-
Builder: Returns a Widget
AsyncSnapshot is used to store the recent state of the future. There are only two cases of this state: one is data and the other is error state. AsyncSnapshot also has ConnectionState states: None: Future not delivered, waiting: waiting, active: TODO, done: completed
FutureBuilder is used to determine which widigets to display on the current page based on the state of the Future. For example, the Future will display the loading box when it is waiting, and the content when it is finished.
One thing to note is that when the state is done, there may be two cases: one is the future succeeds, the other is the future fails, and there is an exception inside. In this case, we should not obtain data, but judge snap. HasData to judge.
The sample
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
if (snap.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snap.connectionState == ConnectionState.done) {
if (snap.hasData) {
return DemoWidget();
} else {
returnText(snap.error); }}throw "should not happen"; }); }}// Make it leaner
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
// If there is data
if (snap.hasData) {
return DemoWidget();
}
// If an error occurs
if (snap.hasError) {
return Text(snap.error);
}
// The loading box is displayed while waiting
returnCircularProgressIndicator(); }); }}Copy the code
Source analyses
/// State for [FutureBuilder].
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
//....
@override
void initState() {
// If there is no initial value, Wie None is set first, if there is, the initial value is passed
_snapshot = widget.initialData == null
? AsyncSnapshot<T>.nothing()
: AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
_subscribe();
}
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if(oldWidget.future ! = widget.future) {//...._subscribe(); }}void _subscribe() {
if(widget.future ! =null) {
final Object callbackIdentity = Object(a); _activeCallbackIdentity = callbackIdentity; widget.future! .then<void>((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
// When the task is complete, pass the data to _snapshot and refresh
setState(() {
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error, StackTrace stackTrace) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
// An error occurred
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
});
}
assert(() {
// Determine the debugging status
if(FutureBuilder.debugRethrowError) {
Future<Object>.error(error, stackTrace);
}
return true; } ()); });// Not complete is a wait state_snapshot = _snapshot.inState(ConnectionState.waiting); }}Copy the code
Source code is actually very simple, take a close look at the whole process
StreamBuilder
introduce
The above FutureBuilder can only give us one value, whereas the StreamBuildder can give us a string of values, such as:
final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);
stream.listen((event) {
print(event);
});
Copy the code
The sample
class _MyHomePageState extends State<MyHomePage> {
final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
color: Colors.white,
child: DefaultTextStyle(
style: TextStyle(fontSize: 30, color: Colors.black),
child: StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
switch (snap.connectionState) {
case ConnectionState.none:
return Text("NONE: No data stream");
break;
case ConnectionState.waiting:
return Text("WAITING for a data stream");
break;
case ConnectionState.active:
if (snap.hasData) {
return Text(snap.data.toString());
}
if (snap.hasError) {
return Text(snap.error.toString());
}
break;
case ConnectionState.done:
return Text("DONE: Data stream closed");
break;
}
returnCircularProgressIndicator(); }),),); }}Copy the code
FutureBuilder is the same as FutureBuilder, but with an active state. This state is not said because ** is not used, which means whether the data stream is active **. If it is active, it can fetch its value
How to create and common functions
-
Use stream.periodic to create a data Stream, as shown in the example above
-
How a file is read
File("").openRead().listen((event) { }) Copy the code
The read file information is forwarded to us as a data stream
-
Using StreamController
final controller = StreamController(); controller.stream.listen((event) { print('$event'); }); controller.sink.add(12); controller.sink.add(13); controller.sink.add(14); controller.sink.add(15); controller.sink.addError("Error"); // No add operations can be performed after closing controller.close(); Copy the code
The StreamController method is much better than the periodic method and allows you to freely add data to the data stream.
It is important to note that shutdown should be performed after use, otherwise resources will leak and the FLUTTER will always warn.
If you add more than one listener, the listener will be saved. If you add more than one listener, the listener will be saved.
final controller = StreamController.broadcast(); Copy the code
Just use broadcast() during creation
One obvious difference between the two is caching. The default creation method is caching, while broadcast does not cache, as follows:
final controller = StreamController(); controller.sink.add(12); controller.sink.add(13); controller.sink.add(14); controller.sink.add(15); controller.sink.addError("Error"); controller.stream.listen((event) { print('$event'); }, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE')); // No add operations can be performed after closing controller.close(); Copy the code
-
This way, even if the data was added first, the callback will print out the data that was added before
final controller = StreamController.broadcast(); controller.sink.add(12); controller.sink.add(13); controller.sink.add(14); controller.sink.add(15); controller.sink.addError("Error"); controller.stream.listen((event) { print('$event'); }, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE')); controller.stream.listen((event) { print('Copy:$event'); }); // No add operations can be performed after closing controller.close(); Copy the code
This method does not print the previous data. So these two approaches are kind of like sticky and non-sticky events in EventBus, and each of them has a role to play and in addition,
-
map
Events can also be changed or modified using map, as follows:
controller.stream.map((event) => "Map: $event").listen((event) { print('$event'); }); Copy the code
-
where
In addition to the Map method, another useful method to filter events is where
controller.stream .where((event) => event > 13) .map((event) => "Map: $event") .listen((event) { print('$event'); }); Copy the code
-
distinct
The event will not be received if the data is the same as the last one.
-
Syntactic sugar async *
Similar to the use of async await, as follows
void main() { getTime().listen((event) { print('$event'); }); } Stream<DateTime> getTime() async* { while (true) { Future.delayed(Duration(seconds: 1)); yield DateTime.now(); }}Copy the code
-
Source analyses
StreamBuilder inherits from StreamBuilderBase with the following core
void _subscribe() {
if(widget.stream ! =null) { _subscription = widget.stream! .listen((T data) { setState(() { _summary = widget.afterData(_summary, data); }); }, onError: (Objecterror, StackTrace stackTrace) { setState(() { _summary = widget.afterError(_summary, error, stackTrace); }); }, onDone: () { setState(() { _summary = widget.afterDone(_summary); }); }); _summary = widget.afterConnected(_summary); }}Copy the code
StreamBuilder makes small games
StreamBuilder is a very useful tool for daily development. This time, we will use StreamBuilder to create a small game.
From the above animation, it can be divided into three parts. The first part is the keyboard at the bottom, the second part is the falling topic, and the third part is the result of the score.
Now let’s implement this little game
-
KeyPad
class KeyPad extends StatelessWidget { final StreamController inputController; final StreamController scoreController; const KeyPad(this.inputController, this.scoreController); @override Widget build(BuildContext context) { return GridView.count( crossAxisCount: 3, childAspectRatio: 2, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(0.0), children: List.generate( 9, (index) => FlatButton( shape: RoundedRectangleBorder(), onPressed: () { inputController.sink.add(index + 1); scoreController.add(2 -); }, child: Text("${index + 1}"), color: Colors.primaries[index], ), ), ); }}Copy the code
KeyPad receives two streams, one for input and the other for scores. The KeyPad at the bottom is a GridView that sends the input number when clicked on the corresponding button.
-
Puzzle
class Puzzle extends StatefulWidget { final Stream inputStream; final StreamController scoreController; const Puzzle({Key key, this.inputStream, this.scoreController}) : super(key: key); @override _PuzzleState createState() => _PuzzleState(); } class _PuzzleState extends State<Puzzle> with SingleTickerProviderStateMixin { int a, b; double x; Color color; AnimationController _controller; @override void initState() { _controller = AnimationController(vsync: this); reset(); _controller.addStatusListener((status) { // End of animation if (status == AnimationStatus.completed) { reset(100.0); widget.scoreController.sink.add(- 3); }}); widget.inputStream.listen((event) {if (event == a + b) { reset(100.0); widget.scoreController.sink.add(7); }});super.initState(); } reset([from = 0.0]) { a = Random().nextInt(5) + 1; b = Random().nextInt(5); x = Random().nextDouble() * 280; _controller.duration = Duration(milliseconds: Random().nextInt(5000) + 5000); color = Colors.primaries[Random().nextInt(Colors.primaries.length)][200]; _controller.forward(from: from); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Positioned( top: 700 * _controller.value - 100, left: x, child: Container( decoration: BoxDecoration( color: color.withOpacity(0.5), border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(24)), padding: EdgeInsets.all(8), child: Text("$a + $b ", style: TextStyle(fontSize: 24)))); }); }}Copy the code
Puzzle also receives the Stream of inputs and scores, and creates an animation. In initState, it listens for the animation and input events, and when the animation ends it means it didn’t answer the question correctly, resets it, and deducts points. After receiving the input event, it calculates whether the result is true, resets it, and adds points
The reset method is used to produce the position of the subject and X-axis and the execution time of the animation, and finally to start the animation
The AnimatedBuilder is used to listen for the animation and reset setState() when the animation value changes. Inside is a small button that records the topic. Note that the background color is 0.5 opacity
-
Score calculation and concatenation of the two widgets above
class GemsPage extends StatefulWidget { @override _GemsPageState createState() => _GemsPageState(); } class _GemsPageState extends State<GemsPage> { final _inputController = StreamController.broadcast(); final _scoreController = StreamController.broadcast(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: StreamBuilder( stream: _scoreController.stream.transform(TallyTransformer()), builder: (context, snapshot) { if (snapshot.hasData) { return Text("Score:${snapshot.data}"); } return Text("Score: 0"); }, ), ), body: Stack( children: [ ...List.generate( 8, (index) => Puzzle( inputStream: _inputController.stream, scoreController: _scoreController), ), Align( alignment: Alignment.bottomCenter, child: KeyPad(_inputController, _scoreController), ) ], ), ); } @override void dispose() { _scoreController.close(); _inputController.close(); super.dispose(); }}class TallyTransformer implements StreamTransformer { int sum = 0; StreamController _controller = StreamController(); @override Stream bind(Stream<dynamic> stream) { stream.listen((event) { sum += event; _controller.add(sum); }); return _controller.stream; } ///Type checking @override StreamTransformer<RS, RT> cast<RS, RT>() => StreamTransformer.castFrom(this); } Copy the code
Reference: B station Wang Shu is not bald
If this article is helpful to your place, we are honored, if there are mistakes and questions in the article, welcome to put forward!