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!