Flutter - Stream 浅析

Stream

StreamDart 提供的一种数据流订阅管理的"工具",Stream 可以接收任何对象,接收的对象通过 StreamControllersink 进行添加,然后通过 StreamController 发送给 Stream,通过 listen 进行监听,listen 会返回一个 StreamSubscription 对象,StreamSubscription 可以操作对数据流的监听,例如 pauseresumecancel

分类

Single-subscription Stream

单订阅 stream,整个生命周期只允许有一个监听,如果该监听 cancel 了,也不能再添加另一个监听,而且只有当有监听了,才会发送数据,主要用于文件 IO 流的读取等。

StreamController _controller = StreamController();

Broadcast Stream

广播 stream,允许有多个监听,当添加了监听后,如果流中有数据存在就可以监听到数据,这种类型,不管是否有监听,只要有数据就会发送,用于需要多个监听的情况。

StreamController _controller = StreamController.broadcast();

简单使用:

  // 创建单订阅类型 `StreamController`
  StreamController _controller = StreamController(); 
   
  // 创建 广播订阅类型 `StreamController`
  //StreamController _controller = StreamController.broadcast();
  
  late Sink _sink;
  late StreamSubscription _subscription;

  @override
  void initState() {
    super.initState();
    // _sink 用于添加数据
    _sink = _controller.sink;
    
    // 添加数据,stream 会通过 `listen` 方法打印
    _sink.add('A');
    
    // _controller.stream 会返回一个单订阅 stream,
    // 通过 listen 返回 StreamSubscription,用于操作流的监听操作
    _subscription = _controller.stream.listen((data) => print('Listener: $data'));
    
    _sink.add('B');
    //调用 pause 方法后,stream 被堵住了,数据不继续发送了
    _subscription.pause(); 
    _sink.add('C'); 
    
    //调用 resume 方法,重新开始发送数据
    _subscription.resume();
    _sink.add('D');
  }

大家自行验证分别使用单订阅 / 广播类型的 StreamController 的输出。

StreamBuilder

        前面提到了 stream 通过 listen 进行监听数据的变化,Flutter 就为我们提供了这么个部件 StreamBuilder 专门用于监听 stream 的变化,然后自动刷新重建。接着来看下源码

class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
    const StreamBuilder({
      Key? key,
      this.initialData,
      Stream<T>? stream,
      required this.builder,
    }) : assert(builder != null),
         super(key: key, stream: stream);
     
    final AsyncWidgetBuilder<T> builder;
    final T? initialData;
         
    @override
    AsyncSnapshot<T> initial() => initialData == null
        ? AsyncSnapshot<T>.nothing()
        : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
        
     Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
     
     ...
}

   StreamBuilder 必须传入一个 AsyncWidgetBuilder 参数,初始值 initialData 可为空, stream 用于监听数据变化,接着看下 父类StreamBuilderBaseState 的源码;

/// State for [StreamBuilderBase].
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
  StreamSubscription<T>? _subscription;
  late S _summary;

  @override
  void initState() {
    super.initState();
    _summary = widget.initial();
    _subscribe();
  }

  @override
  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.stream != widget.stream) {
      if (_subscription != null) {
        _unsubscribe();
        _summary = widget.afterDisconnected(_summary);
      }
      _subscribe();
    }
  }

  @override
  Widget build(BuildContext context) => widget.build(context, _summary);

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

  void _subscribe() {
    if (widget.stream != null) {
      _subscription = widget.stream!.listen((T data) {
        setState(() {
          _summary = widget.afterData(_summary, data);
        });
      }, onError: (Object error, StackTrace stackTrace) {
        setState(() {
          _summary = widget.afterError(_summary, error, stackTrace);
        });
      }, onDone: () {
        setState(() {
          _summary = widget.afterDone(_summary);
        });
      });
      _summary = widget.afterConnected(_summary);
    }
  }

  void _unsubscribe() {
    if (_subscription != null) {
      _subscription!.cancel();
      _subscription = null;
    }
  }
}

通过上面的源码分析,StreamBuilder 也是通过 setState 方法进行刷新界面,但是我们在页面调用 setState 刷新的话,会把整个界面都进行重构,但是通过 StreamBuilder 的话,只刷新其 builder

示例

在了解完Stream & StreamBuilder之后,重构 flutter 计数器demo

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

class CounterPage extends StatefulWidget {
  @override _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;
  final StreamController<int> _streamController = StreamController<int>();

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

  @override Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text('Stream version of the Counter App')
      ),
      body: Center(
        child: StreamBuilder<int>(
          stream: _streamController.stream,
          initialData: _counter,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
            return Text('You hit me: ${snapshot.data} times');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add), 
        onPressed: () {
          _streamController.sink.add(++_counter);
        }));
  }
}

RxDart

RxDart 继承了原生Dart Stream Api,并且对 Stream 做了进一步封装,RxDart 提供了三种 Subject,都是广播流。

PublishSubject

PublishSubject 仅向监听器发送在订阅之后添加的 Stream 的事件

final subject = PublishSubject<int>();

// observer1 will receive all data and done events
subject.stream.listen(observer1);
subject.add(1);
subject.add(2);

// observer2 will only receive 3 and done event
subject.stream.listen(observe2);
subject.add(3);
subject.close();

BehaviorSubject

BehaviorSubject 会将最后发送的事件发送给刚刚订阅的监听器

final subject = BehaviorSubject<int>();

subject.stream.listen(print); // prints 1,2,3

subject.add(1);
subject.add(2);
subject.add(3);

subject.stream.listen(print); // prints 3
subject.stream.listen(print); // prints 3
subject.stream.listen(print); // prints 3

ReplaySubject

当有数据添加了,但是还没有监听的时候,它会将数据存储下来,等到有监听了,ReplaySubject 会将Strean已经发出的所有事件再发送到新的监听器。

final subject = ReplaySubject<int>();

subject.add(1);
subject.add(2);
subject.add(3);

subject.stream.listen(print); // prints 1, 2, 3
subject.stream.listen(print); // prints 1, 2, 3
subject.stream.listen(print); // prints 1, 2, 3

        以上三种 sbuject 均继承自 Subject,在add 方法中,除了向StreamController 中 add event,还有一个 onAdd 的空方法,交给具体的子类做实现; ReplaySubject 重写了 onAdd 方法,将event 保存进一个队列 Queue 中, 而 BehaviorSubject 重新 onAdd 方法,将event保存进 wrapper 变量中,而 PublishSubject 并没有重写 onAdd 方法;

@override
void add(T event) {
  ...  
  _add(event);
}
  
void _add(T event) {
  onAdd(event);
  _controller.add(event);
}

void onAdd(T event) {}

类图

 

 

BLoC

虽然利用StreamBuild 可以减少setState 的刷新页面,但是从上述demo中可以看出,逻辑处理都在page 中,对于复杂的页面将导致page浮肿不利于后期业务的维护,所以新的编程思想应运而生----业务逻辑组件 Business Logic Component

首先亮出BLoC的设计思路图:

 

概念

在深入研究 BLoC 架构的逻辑之前,让我们先了解一下它的主要概念。

  • 事件和操作 是用户与 UI 交互时的输入:例如,滑动或滚动。
  • 状态 是对这些操作的反应,它们根据用户与接口交互所发起的事件而变化。
  • BLoC 是负责业务逻辑的组件。它将事件转换为状态,并且是接收信息并随之作出响应的处理元素。
  • 是用户界面 (UI) 和 BLoC 对作出反应的异步数据流。

BLoC 架构的逻辑为:当用户通过与 UI 交互执行操作时,此操作的相关信息将发送至 BLoC 组件。然后,BLoC 组件处理和解释这些信息,并通过更改 UI 组件的状态做出响应。

示例

重新使用BLoC 重构计数器demo

BlocProvider( 
  child: CounterPage(), 
  bloc: CounterBloc()
);

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    
    final CounterBloc _bloc = BlocProvider.of<CounterBloc>(context);
    return Scaffold(
      body: SafeArea(
          child: Container(
            child: StreamBuilder(
              initialData: _bloc.count,
              stream: _bloc.countStream,
              builder: (_, snapshot) => 
                Text(
                  '${snapshot.data}', 
                ),
            ),
          )),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _bloc.add(), 
        child: Icon(Icons.add),
      ),
    );
  }
}

class CounterBloc extends BaseBloc {
  int _count = 0;
  int get count => _count;

  StreamController<int> _countController = StreamController.broadcast();

  Stream<int> get countStream => _countController.stream; 

  void add() {
    _count += 1;
    _countController.sink.add(_count);
  }

  @override
  void dispose() {
    _countController.close();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值