Fluent core Future/stream/bloc

Keywords: Design Pattern Flutter dart

Future (asynchronous operation)

  Future has three statuses: unfinished, completed with value and completed with exception. Using future can simplify event tasks. In Dart, future objects can be used to represent the results of asynchronous operations. The return type of future is future < T >

There are three ways to deal with the results of Future:

  • then: process the operation execution result or error and return a new Future
  • Catchrror: register a callback to handle errors
  • whenComplete: similar to final, Future will always be called after execution regardless of error or correctness

    Using then to callback, the scenario uses: the UI needs interface data, and some functions are executed asynchronously

# demo1
main() {
  Future f1 = new Future(() {
    print("I was the first");
  });
  f1.then((_) => print("f1 then"));
  print("I am main");
}
# print:
# I'm main
# I was the first
# f3 then

Future is used in conjunction with async/await

    Using chained calls to connect multiple future s together will seriously reduce the readability of the code. async and await keywords are used to implement asynchronous functions. async and await can help us write asynchronous code like synchronous code

Future<String> getStr()async{
  var str = HttpRequest.getString('www.fgyong.cn');
  return str;
}

Use the http request address www.fgyong.cn to obtain data, and then return. How do I receive text?

In fact, it is very simple. You only need to use the await keyword to register the then callback.

main(List<String> args) async {
  String string = await getStr();
  print(string);
}

Equivalent to

main(List<String> args) async {
  getStr().then((value) {
    print(value);
  });
}

stream -- continuous asynchronous operation

If Future represents the result of a single calculation, the flow is a series of results.

Listen to the stream for notifications about the results (data and errors) and the closure of the stream. You can also pause playback while listening to the stream or stop listening until the stream is complete.

It can be said that Future   Used to handle a single asynchronous operation, Stream   Used to handle continuous asynchronous operations.

Stream   It is divided into subscription stream and broadcast stream.

A single subscription stream is allowed to set only one listener before sending the completion event, and the event will not be generated until the listener is set on the stream. The event will be stopped after the listener is cancelled. Even if the first listener is cancelled, it is not allowed to set other listeners on a single subscription stream. Broadcast stream allows multiple listeners to be set, or a new listener can be added after canceling the previous listener.

Stream   There are synchronous flow and asynchronous flow.

The difference between them is that the synchronization flow will be executed in   add,addError   or   close   Method immediately to the streaming listener   StreamSubscription   Send events, and asynchronous flow always sends events after the code execution in the event queue is completed.

There are several ways to create in Dart   Stream

  1. use   async*   Function, function marked as   Async *, we can use   yield   As a keyword and returns   Stream   data
  2. Generate a new stream from an existing   Stream, using   map,where,takeWhile   And other methods.
    Stream<int> countStream(int to) async* {
      for (int i = 1; i <= to; i++) {
        yield i;
      }
    }
    
    Stream stream = countStream(10);
    stream.listen(print);
  3. use   StreamController.
    StreamController<Map> _streamController = StreamController(
      onCancel: () {},
      onListen: () {},
      onPause: () {},
      onResume: () {},
      sync: false,
    );
    
    Stream _stream = _streamController.stream;
  4. use   Future   Object generation
    Future<int> _delay(int seconds) async {
      await Future.delayed(Duration(seconds: seconds));
      return seconds;
    }
    
    List<Future> futures = [];
    for (int i = 0; i < 10; i++) {
      futures.add(_delay(3));
    }
    
    Stream _futuresStream = Stream.fromFutures(futures);

In the actual development process, we basically use the StreamContoller to create streams. Monitoring uses StreamBuilder to execute when the flow changes, for example:

import 'dart:async';
import 'package:flutter/material.dart';

class StreamCounter extends StatefulWidget {
  @override
  _StreamCounterState createState() => _StreamCounterState();
}

class _StreamCounterState extends State<StreamCounter> {
  // Create a StreamController
  StreamController<int> _counterStreamController = StreamController<int>(
    onCancel: () {
      print('cancel');
    },
    onListen: () {
      print('listen');
    },
  );

  int _counter = 0;
  Stream _counterStream;
  StreamSink _counterSink;

  // Send events to Stream using StreamSink when_ When the counter is greater than 9, call the close method to close the flow.
  void _incrementCounter() {
    if (_counter > 9) {
      _counterSink.close();
      return;
    }
    _counter++;
    _counterSink.add(_counter);
  }

  // Active shutdown flow
  void _closeStream() {
    _counterStreamController.close();
  }

  @override
  void initState() {
    super.initState();
    _counterSink = _counterStreamController.sink;
    _counterStream = _counterStreamController.stream;
  }

  @override
  void dispose() {
    super.dispose();
    _counterSink.close();
    _counterStreamController.close();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stream Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            // Displaying and updating the UI using StreamBuilder
            StreamBuilder<int>(
              stream: _counterStream,
              initialData: _counter,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  return Text(
                    'Done',
                    style: Theme.of(context).textTheme.bodyText2,
                  );
                }

                int number = snapshot.data;
                return Text(
                  '$number',
                  style: Theme.of(context).textTheme.bodyText2,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          SizedBox(width: 24.0),
          FloatingActionButton(
            onPressed: _closeStream,
            tooltip: 'Close',
            child: Icon(Icons.close),
          ),
        ],
      ),
    );
  }
}

bloc - state management

From the above, we know that stream is a necessary knowledge to use bloc. A basic use of bloc is cubit. Cubit is similar to the simplified version of bloc. Cubit can be used on some small projects. It is best to use bloc for the data maintained by the project. The following is the core knowledge and use cases of bloc

bloc processing diagram:

  • Bloc mode
    • bloc: logical layer
    • state: data layer
    • event: all interactive events
    • view: page

StreamController and StreamBuilder: the combination of the two can easily refresh local widgets. Counting example

  • view: the Stream stream must have a close operation. Here you need to use StatefulWidget and its dispose callback
class StreamPage extends StatefulWidget {
  const StreamPage({Key? key}) : super(key: key);

  @override
  _StreamPageState createState() => _StreamPageState();
}

class _StreamPageState extends State<StreamPage> {
  final logic = StreamLogic();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc-Bloc example')),
      body: Center(
        child: StreamBuilder<StreamState>(
          initialData: logic.state,
          stream: logic.stream,
          builder: (context, snapshot) {
            return Text(
              'Click ${snapshot.data!.count} second',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => logic.increment(),
        child: Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    logic.dispose();
    super.dispose();
  }
}
  • logic: the Stream data source is generic and can directly use the basic type. The entity is used here to extend more data later
class StreamLogic {
  final state = StreamState();

  // Instantiate flow controller
  final _controller = StreamController<StreamState>.broadcast();

  Stream<StreamState> get stream => _controller.stream;

  void increment() {
    _controller.add(state..count = ++state.count);
  }

  void dispose() {
    // Close the flow controller and free up resources
    _controller.close();
  }
}
  • state
class StreamState {
  int count = 0;
}

Posted by tomm098 on Fri, 19 Nov 2021 20:21:26 -0800