Flutter 異步函數 Stream 及 BloC使用詳解

概念

Stream是用於接收異步事件數據的異步函數,它們在一些耗時操作之後返回數據,比如像 IO操作。Stream可以接收多個Future異步操作的結果(成功或失敗)。另外,Stream 可以被訂閱Subscription、StreamController進行管理。

BLoC是Google團隊給出的一套“反應式應用”的開發架構。其中涉及到Stream的使用。

好了,閒話少說,下文將依次爲大家展示各個功能該如何進行使用。

 

1、Stream 基礎示例

通過調用 Stream 的 fromFuture 方法,可以放入一個 future對象 處理異步操作,接着調用 listen方法 拿到future返回的操作結束 data ,如果出現異常也可以在onError方法中進行打印。示例如下:

Stream.fromFutures([
  // 1秒後返回結果
  Future.delayed(new Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 拋出一個異常
  Future.delayed(new Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒後返回結果
  Future.delayed(new Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){

});

輸出日誌打印結果:

I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3

 2、StreamSubscription訂閱

通過StreamSubscription對象可以將訂閱一個 Stream 對象,從而可以對 Stream 進行暫停、喚醒、取消的管理操作。

 StreamSubscription _streamSubscription;
 @override
 void initState() {
    super.initState();
    Stream<String> _streamDemo = Stream.fromFuture(fetchData());

    _streamSubscription =
        _streamDemo.listen(onData, onError: onError, onDone: onDone);

  }
 //創建一個Future實例
  Future<String> fetchData() async {
    String result ;
    await Future.delayed(Duration(seconds: 3), () {
      return "future 3 seconds is ok!!!";
    }).then((data){
      result= data;
      print("result:$result");
    });
    return result;
  }

 void onData(String data) {
    print("onData:$data");
  }

  void onError(error) {
    print(error);
  }

  void onDone() {
    print("Done");
  }
 
 void _onPauseStream() {
    _streamSubscription.pause();
  }

  void _onResumeStream() {
    _streamSubscription.resume();
  }

  void _onCancelStream() {
    _streamSubscription.cancel();
  }

3、StreamController控制器

(1)、簡單使用

StreamController是對Stream能力的擴展,可以調用.stream.listen就可以監聽數據;調用.add可以放入一個Future對象返回的數據。

//....省略繼承 StatefulWidget 部分代碼
@override
void initState() {
    super.initState();
    //創建控制
    StreamController<String> _streamController = StreamController<String>();
    //監聽Stream
    _streamController.stream.listen(onData, onError: onError, onDone: onDone);
    //獲取Future
     String data = await fetchData();
    //添加控制
     _streamController.add(data);
}

@override
void dispose() {
    //關閉控制
    _streamController.close();
    super.dispose();
}

 //創建一個Future實例
  Future<String> fetchData() async {
    String result ;
    await Future.delayed(Duration(seconds: 3), () {
      return "future 3 seconds is ok!!!";
    }).then((data){
      result= data;
      print("result:$result");
    });
    return result;
  }

(2)StreamSink 池

往StreamController中添加數據可以用StreamSink的add方法。StreamSink對象通過StreamController.sink拿到實例。

//....省略繼承 StatefulWidget 部分代碼
@override
void initState() {
    super.initState();
    //創建控制
    StreamController<String> _streamController = StreamController<String>();
    //創建水池
    StreamSink<String> _streamSink = _streamController.sink;
    //監聽Stream
    _streamController.stream.listen(onData, onError: onError, onDone: onDone);
    //獲取Future
     String data = await fetchData();
    //添加控制_streamController.add(data);
    _streamSink.add(data);
}

@override
void dispose() {
    //關閉控制
    _streamController.close();
    super.dispose();
}

 //創建一個Future實例
  Future<String> fetchData() async {
    String result ;
    await Future.delayed(Duration(seconds: 3), () {
      return "future 3 seconds is ok!!!";
    }).then((data){
      result= data;
      print("result:$result");
    });
    return result;
  }

(3)多次訂閱

有兩種類型的Stream,一種是隻能夠用一個訂閱,另一種是可以有多次訂閱。只需要修改StreamController的創建方式,將StreamController<String>();修改成調用StreamController的broadcast方法。

   _streamController = StreamController<String>();//單次訂閱


   _streamController = StreamController.broadcast(); //多次訂閱

4、StreamBuilder 構建部件

使用Flutter中的 StreamBuilder 可以根據Stream的數據進行構建小部件。如果Stream的數據有變化就會刷新界面,不需要再手動調用SetState方法更新UI了。

//....省略繼承 StatefulWidget 部分代碼
@override
void initState() {
    super.initState();
    //創建控制
    StreamController<String> _streamController = StreamController<String>();
    //創建水池
    StreamSink<String> _streamSink = _streamController.sink;
    //監聽Stream
    _streamController.stream.listen(onData, onError: onError, onDone: onDone);
    //獲取Future
     String data = await fetchData();
    //添加數據
    _streamSink.add(data);
}

@override
void dispose() {
    //關閉控制
    _streamController.close();
    super.dispose();
}

//創建一個Future實例
Future<String> fetchData() async {
    String result ;
    await Future.delayed(Duration(seconds: 3), () {
      return "future 3 seconds is ok!!!";
    }).then((data){
      result= data;
      print("result:$result");
    });
    return result;
}

//Widget
@override
Widget build(BuildContext context) {
    return Container(
        child: StreamBuilder(
              //設置Stream
              stream: _streamController.stream,
              //初始化值
              initialData: '無須手動setState',
              //構建一個Text
              builder: (context, snapshot) {
                
                return Text(snapshot.data);
              },
            ),
    );
}
 

5、BLoC 業務邏輯組件

BLoC 是英文Business Logic Component的縮寫,翻譯成中文的意思是“業務邏輯組件”。簡單來說,其實就是將用戶需要的一些邏輯單獨的拿出來,放到一個類裏面,這種類就叫做BLoC。

前面學到StreamController中的sink,它的功能可以往Stream上面添加數據,並且會構建小部件改變UI界面。我們可以創建一個BLoC類,裏面添加一個sink,從而監聽Stream的變化。

下面,我們用一個小案例來加以說明。

(1)首先,創建簡單UI界面

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

void main() {
  runApp(
    MaterialApp(
      title: 'bloc訓練',
      home: Scaffold(
          appBar: AppBar(
            title: Text('bloc訓練'),
          ),
          //創建 界面 類
          body: BlocDemo(),

          //創建 按鈕 類
          floatingActionButton: CounterActionButton(),
      ),
    ),
  );
}

BloCDemo 界面類

class BlocDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
   
    return Center(
      child: ActionChip(
          label: Text('0'),
          onPressed: () {
          },
       ),
    );
  }
}

CounterActionButton 類

class CounterActionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
   
    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () {
      },
    );
  }
}

(2) 使用InheritedWidget 傳遞 BLoC

  Inherited是繼承的意思。InheritedWidget 用來在小部件繼承之間傳遞數據。

使用時先創建一個類繼承InheritedWidget,然後在類中設置其他小部件需要的數據,最後將這個類放在小部件樹的某一個位置,這樣下面的小部件就可以直接訪問到InheritedWidget小部件裏設置的數據。

BloC類 

class BlocCounter {
  void log() {
    print('Bloc');
  }
}

InheritedWidget類

class CounterProvider extends InheritedWidget {
  //繼承部件
  final Widget child;
  final BlocCounter bloc;

  CounterProvider({this.child, this.bloc}) : super(child: child);

  static CounterProvider of(BuildContext context) =>
      context.inheritFromWidgetOfExactType(CounterProvider);

  @override
  bool updateShouldNotify(CounterProvider oldWidget) {
    return true;
  }
}

使用InheritedWidget類獲取BloC類,部分代碼修改如下:

爲了能夠讓更多小部件樹能夠獲取到InheritedWidget類中提供的數據——BloC,這裏放到Scaffold部件的父級位置。

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

void main() {
  runApp(
    MaterialApp(
      title: 'bloc',
      //放置 InheritedWidget類
      home: CounterProvider(
        //創建 BloC 類
        bloc: BlocCounter(),
        child: Scaffold(
          appBar: AppBar(
            title: Text('bloc訓練'),
          ),
            //創建 界面類
          body: BlocDemo(),
            //創建 按鈕類
          floatingActionButton: CounterActionButton(),
        ),
      ),
    ),
  );
}

BloCDemo 界面類

class BlocDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //通過InheritedWidget獲取BloC類
    BlocCounter _bloc = CounterProvider
        .of(context)
        .bloc;

    return Center(
      child: ActionChip(
          label: Text("0"),
          onPressed: () {
            //顯示log
            _bloc.log();
          },
        ),
    );
  }
}

CounterActionButton 類

class CounterActionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //通過InheritedWidget拿到BloC類
    BlocCounter _bloc = CounterProvider
        .of(context)
        .bloc;

    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () {
        //顯示log
        _bloc.log();
      },
    );
  }
}

(3)用 Stream 輸出數據

我們可以通過點擊按鈕調用 StreamSink的add方法,往Stream中添加一個數據。BloC中監聽該Stream的變化。然後再創建一個Stream,並且使用StreamBuilder構建小部件,實現動態交互刷新UI界面。

class BlocCounter {
  //創建 Stream
  final _streamController = StreamController<int>();
  //創建 Sink
  StreamSink<int> get counter => _streamController.sink;
  //計數
  int _count = 0;
  //創建 新Stream ,用於接受點擊add的值的變化
  final _streamController2 = StreamController<int>();
  Stream<int> get count => _streamController2.stream;

  BlocCounter() {
    this._streamController.stream.listen(onData);
  }

  void onData(int data) {
    print('$data');
    _count = data + _count;
    //將值添加到Stream中,觸發StreamBuilder更新界面
    _streamController2.add(_count);
  }

  void disponse() {
    _streamController.close();
    _streamController2.close();
  }

  void log() {
    print('Bloc');
  }
}

BloCDemo 界面類

class BlocDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //通過InheritedWidget獲取BloC類
    BlocCounter _bloc = CounterProvider
        .of(context)
        .bloc;

    return Center(
      child: StreamBuilder(builder: (context, snapshot) {
        return ActionChip(
          label: Text('${snapshot.data}'),
          onPressed: () {
            //往sink 裏添加數據 1
            _bloc.counter.add(1);
            //打印log
            _bloc.log();
          },
        );
      }, initialData: 0,
     stream: _bloc.count),//設置Stream來源
    );
  }
}

CounterActionButton 類

class CounterActionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //通過InheritedWidget拿到BloC類
    BlocCounter _bloc = CounterProvider
        .of(context)
        .bloc;

    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () {
        //顯示log
        _bloc.log();
        //往sink裏添加數據 1
        _bloc.counter.add(1);
      },
    );
  }
}

 


歡迎訂閱公衆號:數據結構、算法、面試經驗、每日新聞、閒聊趣文等。歡迎一起學習!

歡迎加入Android QQ交流學習羣:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章