前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter ——状态管理 | StreamBuild

Flutter ——状态管理 | StreamBuild

原创
作者头像
CatEatFish
修改2020-07-09 14:24:44
2.7K0
修改2020-07-09 14:24:44
举报
文章被收录于专栏:干活分享干活分享

1.什么是stream?

StreamBuild从字面意思来讲是数据流构建,是一种基于数据流的订阅管理。Stream可以接受任何类型的数据,值、事件、对象、集合、映射、错误、甚至是另一个Stream,通过StreamController中的sink作为入口,往Stream中插入数据,然后通过你的自定义监听StreamSubscription对象,接受数据变化的通知。如果你需要对输出数据进行处理,可以使用StreamTransformer,它可以对输出数据进行过滤、重组、修改、将数据注入其他流等等任何类型的数据操作。

2.stream都有哪些类型

Stream有两种类型:单订阅Stream和广播Stream。单订阅Stream只允许在该Stream的整个生命周期内使用单个监听器,即使第一个subscription被取消了,你也没法在这个流上监听到第二次事件;而广播Stream允许任意个数的subscription,你可以随时随地给它添加subscription,只要新的监听开始工作流,它就能收到新的事件。

2.1 单订阅类型实例
import 'dart:async';

void main() {
  // 初始化一个单订阅的Stream controller
  final StreamController ctrl = StreamController();
  
  // 初始化一个监听
  final StreamSubscription subscription = ctrl.stream.listen((data) => print('$data'));

  // 往Stream中添加数据
  ctrl.sink.add('my name');
  ctrl.sink.add(1234);
  ctrl.sink.add({'a': 'element A', 'b': 'element B'});
  ctrl.sink.add(123.45);
  
  // StreamController用完后需要释放
  ctrl.close();
}
2.2广播类stream
import 'dart:async';

void main() {
  // 初始化一个int类型的广播Stream controller
  final StreamController<int> ctrl = StreamController<int>.broadcast();
  
  // 初始化一个监听,同时通过transform对数据进行简单处理
  final StreamSubscription subscription = ctrl.stream
                          .where((value) => (value % 2 == 0))
                          .listen((value) => print('$value'));

  // 往Stream中添加数据
  for(int i=1; i<11; i++){
    ctrl.sink.add(i);
  }
  
  // StreamController用完后需要释放
  ctrl.close();
}

3.stream有哪些好处?

####3.1.随意操作数据流。

刚才在stream定义那里已经说过了,stream是基于数据流的,从skin管道入口到StreamController提供stream属性作为数据的出口之间,可以对数据做任何操作,包括过滤、重组、修改等等。

####3.2 当数据流变化时,可以刷新小部件。

Stream是一种订阅者模式,当数据发生变化时,通知订阅者发生改变,重新构建小部件,刷新UI。

###4.如何使用streamBuild?

  StreamBuilder<T>(
    key: ...可选...
    stream: ...需要监听的stream...
    initialData: ...初始数据,尽量不要填null...
    builder: (BuildContext context, AsyncSnapshot<T> snapshot){
        if (snapshot.hasData){
            return ...基于snapshot.hasData返回的控件
        }
        return ...没有数据的时候返回的控件
    },
)

下面是一个模仿官方自带demo“计数器”的一个例子,使用了StreamBuilder,而不需要任何setState:

我在代码里注释了步骤(四步):

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

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

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;
  //步骤1:初始化一个StreamController<任何数据> 简单的可以扔一个int,string,开发中经常扔一个网络请求的model进去,具体看你使用场景了。
  final StreamController<int> _streamController = StreamController<int>();

  @override
  void dispose(){
  //步骤2.关流,不管流会消耗资源,同时会引起内存泄漏
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream version of the Counter App')),
      body: Center(
      //步骤3.使用StreamBuilder构造器
        child: StreamBuilder<int>(  // 监听Stream,每次值改变的时候,更新Text中的内容
          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: (){
          // 每次点击按钮,更加_counter的值,同时通过Sink将它发送给Stream;
          // 每注入一个值,都会引起StreamBuilder的监听,StreamBuilder重建并刷新counter
          //步骤4.往StreamBuilder里添加流,数据变了,就用通知小部件
          _streamController.sink.add(++_counter);
        },
      ),
    );
  }
}

###5.具体如何使用?

刚刚介绍了stream的如何使用,是不是感觉还是懵的状态,实例代码仅仅是实例,如何应用到项目中呢?我们的项目不仅仅是一个简单的计数器,接下来我将结合项目,简单讲述一下如何使用streamBuild。这是我司的一张UI。

UI.png
UI.png

要求点击“关注”变为“已关注”

如何去实现的?实现的方法有好多种。

1.这个item是StatefulWidget,点击“关注”,然后setstate(){}

2.使用其他的状态管理去实现。如 【 ScopedModel 】 【 Provide 】 【Bloc】

3.使用 StreamBuild 实现。

我选择使用StreamBuild去实现,稍后我会解释为何要用streambuild 去实现。

import 'dart:async';
import 'package:easy_alert/easy_alert.dart';
import 'package:flutter/material.dart';
import 'package:hongka_flutter/app/Manager/IO/hk_request.dart';
import 'package:hongka_flutter/app/Manager/api/ConfigApi.dart';
import 'package:hongka_flutter/app/Modules/basemodel/focuseItemModel.dart';
import 'package:hongka_flutter/app/Modules/home/info_organization.dart';
//我会省略部分代码,并且注释使用步骤
//步骤一:使用 StatefulWidget,为何要用StatefulWidget?待会解释
class FollowsItem extends StatefulWidget {
  FocuseItemModel focusItemModel;
  String focusType; //不为空就是关注界面,隐藏关注按钮
  int index;
  String studentId;

  FollowsItem(
      {Key key,
      this.focusItemModel,
      this.focusType,
      this.index,
      this.studentId});

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

class _FollowsItemState extends State<FollowsItem> {
  FocuseItemModel focusItemModel;
  String focusType; //不为空就是关注界面,隐藏关注按钮
  String headerImage;
  double screenWidth, marginLeft = 15.0, marginRight = 15.0, marginAll = 30.0;
  int index;
  String studentId;
  //步骤二:声明StreamController
  StreamController<FocuseItemModel> _streamController;

  @override
  void initState() {
    super.initState();
    this.focusItemModel = this.widget.focusItemModel;
    this.focusType = this.widget.focusType;
    this.index = this.widget.index;
    this.studentId = this.widget.studentId;
    //步骤三实现 StreamController<FocuseItemModel>,FocuseItemModel是我的实体类
    _streamController = StreamController<FocuseItemModel>.broadcast();
    //步骤四将数据添加到 _streamController
    _streamController.sink.add(focusItemModel);
  }

  @override
  void dispose() {
  //步骤五:关流
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        _clickItem(context);
      },
      child: Container(
        .....省略无关UI代码
        child: Stack(
          children: <Widget>[
            .....省略无关UI代码
            Positioned(
              right: 0,
              top: 0,
              child: Offstage(
                  offstage: !(focusType == null),
                  child: GestureDetector(
                    onTap: () {
                    //已关注return
                      if (focusItemModel.focusStatus == 1) {
                        return;
                      }
                      //未关注进行网络请求
                      focusOrganizationRequest(focusItemModel.positionId);
                    },
                    //步骤五:使用StreamBuilder构建
                    child: StreamBuilder<FocuseItemModel>(
                        stream: _streamController.stream,//数据流
                        initialData: focusItemModel,//初始值
                        builder: (BuildContext context,
                            AsyncSnapshot<FocuseItemModel> snapshot) {
                          return Image.asset(
                            (snapshot?.data?.focusStatus ?? 0) == 1
                                ? 'images/view/recommend_flowered.png'
                                : 'images/view/recommend_flower.png',
                            width: 54,
                            height: 40,
                          );
                        }),
                  )),
            )
          ],
        ),
      ),
    );
  }

  ///关注红广号
  Future focusOrganizationRequest(String positionId) async {
    String url = 'xxxx';
    var data = {
      'studentId': studentId,
      'positionId': positionId,
    };
    var options = await HK_Request().getRequestOptions();
    var response = await HK_Request()
        .post(url, context, isShowLoading: true, data: data, options: options);
    print(response.toString());
    if (response['status'] == 200) {
    //步骤六:改变model值,并将model 扔到_streamController里,此时数据改变,通知小部件,重新构建
      focusItemModel.focusStatus = 1;
      _streamController.sink.add(focusItemModel);
      Alert.toast(context, '关注成功!',
          position: ToastPosition.center, duration: ToastDuration.short);
      print('关注红广号成功' + response.toString());
    
    } else {
      print('关注红广号错误');
      Alert.toast(context, response['msg'],
          position: ToastPosition.center, duration: ToastDuration.short);
    }
  }

model值改变,streamBuild 通知小部件,并刷新小部件。

问题1 为何选择使用streamBuild

1.方法一使用StatefulWidget,刷新时使用setstate(){},使用setstate(){}刷新,会将整个item 进行重新构建,整个item 仅仅只有“关注”需要改变,其它控件都刷新,会造成资源浪费。

2.方法二使用状态管理bloc,如果使用了bloc,streamBuild中的stream 就因该传bloc< AModel >的数据,如果我其它地方使用也使用了这个item,那么这个stream就应该传bloc< BMode >,此时streamBuild中的stream 类型就不匹配了,这个item 就无法复用了,所以我放弃使用bloc等状态管理

3.为何item 最外层使用StatefulWidget?不是使用streamBuild 就可以不用使用StatefulWidget了吗?

的确使用streamBuild,就可以不使用StatefulWidget。但是 不用StatefulWidget,如何关流? StatelessWidget 没有dispose()方法,不能关流,所以此时还需要使用StatefulWidget。

 @override
  void dispose() {
  //步骤五:关流
    _streamController.close();
    super.dispose();
  }

有群友提出,可以将“关注”的属性提取出来,单独一个bloc去管理,我觉得为了一个按钮的改变,去做很多操作,有点不值得了。当然有兴趣的可以去实现一下。

###问题2.怎样才能不使用StatefulWidget?

bloc+streamBuild,此时的stream是bloc里的,不需要在dispose()方法中去关流,这样就可以放弃使用StatefulWidget了。

6. bloc结合streamBuild 实现状态管理会在下一篇内容中讲解。

本人对于 streamBuild 理解的也不是很深刻,没有往太细节去讲解,只是结合自己的项目去讲解了开发中遇到的问题,希望大家提提意见,共同进步。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.什么是stream?
  • 2.stream都有哪些类型
    • 2.1 单订阅类型实例
      • 2.2广播类stream
      • 3.stream有哪些好处?
      • 问题1 为何选择使用streamBuild
        • 6. bloc结合streamBuild 实现状态管理会在下一篇内容中讲解。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档