Flutter&Dart-非同步程式設計Future、Stream極速入門

旺仔小小饅頭發表於2021-08-11

Dart是單執行緒模型,我們寫的程式碼都執行在同一個執行緒中。如果做了耗時操作,會使應用程式阻塞。Dart中使用Future和Stream編寫非同步任務。

Future

Future是一個不會馬上完成的計算過程,說的通俗一點就是一個非同步執行過程,需要配合async和await一起使用。不會阻塞在此之後的程式碼,等待計算完成後才會返回結果。

類似於JavaScript中的Promiseasyncawait

用法

void main()  {
  requestApi();
  doSomething1();
  doSomething2();
}

doSomething1() {
  print("doSomething1");
}

doSomething2() {
  print("doSomething2");
}

/// 非同步請求資料
Future requestApi() async {
  ....
  return 
}
複製程式碼

由於requestApi() 是個非同步函式,程式執行後,不會等待函式執行完成,下面的程式碼也會立即開始執行。

等待返回結果

如果想在非同步函式返回結果後再做其他操作,可以使用then()方法來監聽。

void main()  {
  requestApi().then((value) {
    /// 結果返回,開始處理
    showApiData(value);
  });
  print("程式開始執行...");
}

............
  
showApiData(dynamic value){
  print("展示獲取的資料: $value");
}
複製程式碼

FutureBuilder

const FutureBuilder({
    Key? key,
    this.future,
    this.initialData,
    required this.builder,
})
複製程式碼

FutureBuilder會基於傳入的future的返回結果來構建Widget。

使用示例

一般用於網路請求後更新UI。

class FutureBuilderDemo extends StatefulWidget {
  const FutureBuilderDemo({Key key}) : super(key: key);

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

class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
  Future<String> futureData;

  @override
  void initState() {
    super.initState();
    /// ui初始化時開始網路請求資料
    futureData = getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('FutureBuilder 測試'),
      ),
      body: Container(
        child: Center(
          child: FutureBuilder<String>(
              future: futureData,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.hasData) {
                  return Text("獲取的資料==${snapshot.data}");
                } else if (snapshot.hasError) {
                  return Icon(Icons.error_outline);
                } else {
                  return CircularProgressIndicator();
                }
              }),
        ),
      ),
    );
  }

  Future<String> getData() async {
    /// 模擬網路請求,等待5秒返回結果
    await Future.delayed(Duration(seconds: 5));

    /// 返回一個錯誤
    // return Future.error("error");
    /// 返回一個成功的結果
    return "Hello,world";
  }
}
複製程式碼

Stream

Stream是一系列非同步事件的序列,相當於一個非同步的跌代器(Iterable)。不同於Future只是一個事件執行過程,Stream可以傳送執行多個事件。使用場景不同。

用法

建立方式有2種,一個是單訂閱的Stream,一種是能多訂閱的流,也就是可以廣播。

訂閱後(listen)會返回一個StreamSubscription,就是一個觀察者。

2種方式的構造方法都有一個可選引數sync,預設為false。

..........
   bool sync = false}) {
return sync
        ? _SyncStreamController<T>(....)
        : _AsyncStreamController<T>(...);
複製程式碼

通過原始碼可以看出,sync為false建立的是一個非同步事件StreamController, sync為true時建立的是一個同步StreamController。因為大部分場景下使用Stream都是為了非同步,所以我們直接不傳入即可。

單訂閱

/// 直接使用建構函式
StreamController<String> _controller = StreamController();
StreamSubscription subscription = _controller.stream.listen((event) {
    print("subscription 接收到 $event");
});
複製程式碼

廣播訂閱

void main() {
  /// 使用工廠方法構造可廣播的Controller
  StreamController _controller = StreamController.broadcast();

  /// 使用多個觀察者訂閱同一個Stream.
  StreamSubscription subscription1 =
      _controller.stream.listen((event) => print("sub1 接收到 $event"));
  StreamSubscription subscription2 =
      _controller.stream.listen((event) => print("sub2 接收到 $event"));
  StreamSubscription subscription3 =
      _controller.stream.listen((event) => print("sub3 接收到 $event"));

  /// 傳送事件後,所有已訂閱的觀察者的都能接收到事件。
  _controller.add("Hello");
}
複製程式碼

執行多次,列印結果都如下不變:

sub1 接收到 Hello
sub2 接收到 Hello
sub3 接收到 Hello

Process finished with exit code 0
複製程式碼

結論:

事件訂閱(listen)的越早,接收到事件的優先順序越高。

釋放資源

使用完事件後或者在Flutter中Widget關閉時,記得也同時關閉Stream。

_controller.stream.listen((event) {}, onDone: () => print("收到onDone事件"));

_controller.close();
subscription.cancel();
複製程式碼

關閉後會自動觸發onDone回撥方法。

StreamBuilder

在介面中,一般使用StreamBuilder來來配合Stream使用。可以實現多狀態介面。

使用示例

/// 定義3種ui狀態
enum UIState { type_1, type_2, type_3 }

class StreamBuilderDemo extends StatefulWidget {
  const StreamBuilderDemo({Key key}) : super(key: key);

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

class _StreamBuilderDemoState extends State<StreamBuilderDemo> {
  StreamController<UIState> _controller;

  @override
  void initState() {
    super.initState();

    /// 初始化controller
    _controller = StreamController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamBuilder測試'),
      ),
      body: Container(
        // height: double.infinity,
        child: Center(
          child: StreamBuilder<UIState>(

              /// 傳入stream
              stream: _controller.stream,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.hasData) {
                  /// hasData代表有接收到事件
                  var data = snapshot.data;
                  Widget widget;

                  ///根據返回不回的型別,展示不同的圖片
                  switch (data) {
                    case UIState.type_1:
                      widget = Icon(Icons.timer, size: 100);
                      break;
                    case UIState.type_2:
                      widget = Icon(Icons.done, size: 100);
                      break;
                    case UIState.type_3:
                      widget = Icon(Icons.ac_unit, size: 100);
                      break;
                  }
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      widget,
                      Text("$data"),
                    ],
                  );
                } else if (snapshot.hasError) {
                  /// 接收到錯誤事件
                  return Icon(Icons.error_outline_rounded, size: 100);
                } else {
                  /// 什麼都沒有,代表沒還有接收到事件
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CircularProgressIndicator(),
                      Text("初始化中,還沒接收到收到狀態"),
                    ],
                  );
                }
              }),
        ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: () => generateState(), child: Icon(Icons.add)),
    );
  }

  /// 隨機生成不同的狀態,傳送事件
  generateState() {
    var randomIndex = Random().nextInt(UIState.values.length);
    _controller.add(UIState.values[randomIndex]);
  }

  @override
  void dispose() {
    super.dispose();

    /// 回收資源
    _controller.close();
  }
}
複製程式碼

相關文章