Introduction to Flutter Stream and the Use of Some Operators

Time:2019-9-26

Preface

For new contactsFlutterFor students,Stream(flow) is a relatively abstract and relatively difficult thing to understand. To be exactStreamNot at allFlutterCharacteristic, butDartThe Library of language itself.StreamandFutureAll locateddart:asyncCore libraries are two masters of asynchronous operation in Dart. So it can be used more than justFlutterIt can be used for anything.DartLanguage implementation.

When we first started learning Flutter, we basically used it.StatefulWidgetandsetState((){})To refresh the data in the interface, I can use the stream completely when I am proficient in using it.StatelessWidgetA farewellStatefulWidgetIt also achieves data refresh effect.

Stream classification

Flows can be divided into two categories:

  • Single Subscription Stream, which has at most one listener
  • Broadcast streams, which can have multiple listeners

Stream Creation

  • Stream<T>.fromFuture receives a Future object as a parameter

    Stream<String>.fromFuture(getData());
    
    Future<String> getData() async{
        await Future.delayed(Duration(seconds: 5));
        Return "Returns a Future object";
      }
  • Stream<T>.fromIterable receives a collection object as a parameter

    Stream<String>.fromIterable(['A','B','C']);
  • Stream<T>.fromFutures receives a Future collection object as a parameter

    Stream<String>.fromFutures([getData()]);
  • Stream<T>.periodic receives a Duration object as a parameter

    Duration interval = Duration(seconds: 1);
    Stream<int> stream = Stream<int>.periodic(interval);

Operation mode and use

Create a Stream object in Stream < T >. periodic mode

The flow used to create a periodic send event is as follows

/// Used to create a stream that sends an infinite event every second and prints out the value
void _stream() async{
    Duration interval = Duration(seconds: 1);
    Stream<int> stream = Stream.periodic(interval, (data) => data);
    await for(int i in stream ){
      print(i); 
    }
}

// (data) {return data;} The above sentence is used to return values. If omitted, the printed values are null.

.

  • Stream<T>.take(int count)

    It creates a stream that sends events indefinitely every second. If we want to specify that only 10 events are sent, take. Only 0-9 will be printed below.

    void _stream() async{
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        Stream = stream. take (10); // Specify the number of events sent
        await for(int i in stream ){
         print(i);
        }
    }
  • Stream<T>.takeWhile

    In this way, we only set the number of events sent. If we don’t know how many events are sent, we can make a limit on the return value from the results returned. The above results can also be implemented in the following way.

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
    //    stream = stream.take(10);
        stream = stream.takeWhile((data) {
          return data < 10;
        });
        await for (int i in stream) {
          print(i);
        }
      }
  • Stream<T>.skip(int count)

    Skp can specify skipping the first few events, as follows skipping 0 and 1, output 2-9;

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        stream = stream.skip(2);
        await for (int i in stream) {
          print(i);
        }
      }
  • Stream<T>.skipWhile

    You can specify a condition for skipping non-sending events, as follows: skip 0-4 output, output 5-9

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        stream = stream.skipWhile((data) => data<5);
        await for (int i in stream) {
          print(i);
        }
      }
  • Stream<T>.toList()

    Store all data collection in the stream in List and return to Future < List < T > object, 0-9 in ListData

    1. This is an asynchronous method, and the await keyword is required for the result.

    2. This is to wait for Stream to return the result once when the stream ends.

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        List<int> listData = await stream.toList();
        for (int i in listData) {
          print(i);
        }
      }
  • Stream<T>. listen()

    This is a specific way to listen for data streams, consistent with the forEach loop, but returnsStreamSubscription<T>Object, which will output 0-9 as follows, and print out the “flow completed”“

    Look at the source code. This is acceptable.

    StreamSubscription<T> listen(void onData(T event),
          {Function onError, void onDone(), bool cancelOnError});

    1.onDataIt is a method that must be realized to process the received data.

    2.onErrorProcessing when flow errors occur

    3.onDoneFlow completion call

    4.cancelOnErrorWhether to terminate immediately when an error occurs

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        stream.listen((data) {
          print(data);
        }, onError: (error) {
          Print ("flow error");
        }, onDone: () {
          Print ("the stream has been completed");
        }, cancelOnError: false);
      }
  • Stream<T>. forEach()

    This operation andlisten()It’s basically the same way. It’s also a way to monitor streams. It’s just listening.onDataThe following code also outputs 0-9

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        stream.forEach((data) {
          print(data);
        });
      }
  • Stream<T> .length

    To get the total number of events in the waiting stream after all events have been launched, the following code will output 10

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream.periodic(interval, (data) => data);
        stream = stream.take(10);
        var allEvents = await stream.length;
        print(allEvents);
      }
  • Stream<T>.where

    Adding filter conditions to the stream, filtering out some unwanted data, returning true if the condition is satisfied and false if the condition is not satisfied. Here we filter out data larger than 5 and less than 10 in the stream.

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream<int>.periodic(interval, (data) => data);
        stream = stream.where((data)=>data>5);
        stream = stream.where((data)=> data<10);
        await for(int i in stream){
          print(i);
        }
      }
  • stream<T>.map

    There are some transformations of the data in the convection. Here’s how I add 1 to each of Stream’s data.

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream<int> stream = Stream<int>.periodic(interval, (data) => data);
        stream = stream.map((data) => data + 1);
        await for (int i in stream) {
          print(i);
        }
      }
  • Stream<T>.expand

    The data in the convection is extended to output 1,1,2,2,3,3 as follows. .

    void _stream() async {
        Duration interval = Duration(seconds: 1);
        Stream stream = Stream.periodic(interval, (data) => data);
        stream = stream.expand((data)=>[data,data]);
        Stream. listen ((data) => print (data), onError:(error) => print ("error occurred");
      }
  • Stream<S>.transform

    If we need some transformation and control in the process of flow, we need to use transform to receive one.

    Stream Transformer < S, T >, S denotes the type before conversion, T denotes the type of input after conversion. The following code will receive three sets of digital analog input passwords three times, and determine the correct password, and output the correct password and password error at the same time:

    void _stream() async {
        var stream = Stream<int>.fromIterable([123456,234567,678901]);
        var st = StreamTransformer<int, String>.fromHandlers(
            handleData: (int data, sink) {
          if (data == 678901) {
            Sik. add ("The password is entered correctly and the lock is being unlocked... "";
          } else {
            Sink. add ("incorrect password input...");
          }
        });
        stream.transform(st).listen((String data) => print(data),
            OnError: (error) => print ("error");
      }

    Enter the following results

    I/flutter (18980): Error in password input..
    I/flutter (18980): Error in password input..
    I/flutter (18980): The password is entered correctly and the lock is being unlocked...

Stream Controller uses

That’s all.StreamThe basic concepts and usages, the way of creating streams directly above, are not very useful for our own development. We basically use them in the actual development process.StreamContollerTo create streams. From the source code we can knowStreamSeveral construction methods are finally adopted.StreamControllerPackaging was carried out.

Creating StreamController objects and using them

  • Build order subscriptionStreamcontroller
// In Stream Controller, you create a Stream, the Stream we actually manipulate.
StreamController<String> streamController = StreamController();
streamController.stream.listen((data)=> print(data));
streamController.sink.add("aaa");
streamController.add("bbb");
streamController.add("ccc");
streamController.close();

// The above code will output aaa, bbb, CCC

Note: If we add a listen to the above code, the following exception will be reported, so the order subscription stream can only have one listen. In general, we use the order subscription stream mostly, and we can also turn the order subscription stream into a multi-subscription stream.

"error"

  • Constructing multi-listenerStreamControllerThere are two ways

    1. Direct creation of multiple subscriptionsStream

    StreamController<String> streamController = StreamController.broadcast();
        streamController.stream.listen((data){
          print(data);
        },onError: (error){
          print(error.toString());
        });
        streamController.stream.listen((data) => print(data));
        streamController.add("bbb");
    
        // The above code outputs bbb, BBB back

    2. Converting order subscription flow to multi-subscription flow

    StreamController<String> streamController = StreamController();
      Stream stream =streamController.stream.asBroadcastStream();
      stream.listen((data) => print(data));
      stream.listen((data) => print(data));
      streamController.sink.add("aaa");
      streamController.close();
      
      // The above code will output aaa, AAA

Note: When the stream is exhausted, remember to close it and call itstreamController.close()

Stream Builder uses

In front of meStreamCommon ways to do a simple introduction and demonstration, how do we use Flutter? Provided in FlutterWidgetBe named asStreamBuilder,StreamBuilderIt’s actually aStreamBuilderIt keeps track of the latest data in the stream. When the data stream changes, builder method is automatically called to reconstruct it.

  • StreamBuilder’s source code is as follows, need to accept a stream, we can pass in aStreamControllerOfStream

    const StreamBuilder({
        Key key,
        this.initialData,
        Stream<T> stream,
        @required this.builder,
      }) : assert(builder != null),
           super(key: key, stream: stream);
  • UseStreamControllerCombinationStreamBuiderImprove the official counter to refresh the page instead of setState. The code is as follows

    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _count = 0;
      final StreamController<int> _streamController = StreamController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            child: Center(
              child: StreamBuilder<int>(
                  stream: _streamController.stream,
                  builder: (BuildContext context, AsyncSnapshot snapshot) {
                    return snapshot.data == null
                        ? Text("0")
                        : Text("${snapshot.data}");
                  }),
            ),
          ),
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () {
                _streamController.sink.add(++_count);
              }),
        );
      }
    
      @override
      void dispose() {
        _streamController.close();
        super.dispose();
      }
    }

Source code correlation

From the StreamController source code, we can see that the process created inside creates a _SyncStreamController by default, which can read the source code, seeStreamControllerHow to createStreamandStreamSink

factory StreamController(
      {void onListen(),
      void onPause(),
      void onResume(),
      onCancel(),
      bool sync: false}) {
    return sync
        ? new _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
        : new _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
  }

Note: I used both above.streamController.sink.add("aaa");Adding data, also usingstreamController.add("bbb");Method to add data. The actual effect is the same. Looking at the source code, we can see that sink is as follows. In fact, sink is right.StreamControllerOne kind of packaging is the StreamController. add method which is called in the end.

class _StreamSinkWrapper<T> implements StreamSink<T> {
  final StreamController _target;
  _StreamSinkWrapper(this._target);
  void add(T data) {
    _target.add(data);
  }

  void addError(Object error, [StackTrace stackTrace]) {
    _target.addError(error, stackTrace);
  }

  Future close() => _target.close();

  Future addStream(Stream<T> source) => _target.addStream(source);

  Future get done => _target.done;

summary

The above is my development process, right?FlutterinStreamA summary of some simple understandings and common methods used.StreamBuilderandStreamControllerUse it together to achieve a partial refresh effect (analogy to a page with multiple interfaces, each should refresh its own part of the UI control rather than the entire page refresh, when we can useStreamControllerandStreamBuilderTo achieve this effect) If there are any shortcomings or deviations, please correct them.~