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交流学习群:

发布了153 篇原创文章 · 获赞 755 · 访问量 100万+

猜你喜欢

转载自blog.csdn.net/csdn_aiyang/article/details/103138946