Flutter ——状态管理 | StreamBuild

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 ctrl = StreamController.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(
    key: ...可选...
    stream: ...需要监听的stream...
    initialData: ...初始数据,尽量不要填null...
    builder: (BuildContext context, AsyncSnapshot 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 {
  int _counter = 0;
  //步骤1:初始化一个StreamController<任何数据> 简单的可以扔一个int,string,开发中经常扔一个网络请求的model进去,具体看你使用场景了。
  final StreamController _streamController = StreamController();

  @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(  // 监听Stream,每次值改变的时候,更新Text中的内容
          stream: _streamController.stream,
          initialData: _counter,
          builder: (BuildContext context, AsyncSnapshot 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。

Flutter ——状态管理 | StreamBuild_第1张图片
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 {
  FocuseItemModel focusItemModel;
  String focusType; //不为空就是关注界面,隐藏关注按钮
  String headerImage;
  double screenWidth, marginLeft = 15.0, marginRight = 15.0, marginAll = 30.0;
  int index;
  String studentId;
  //步骤二:声明StreamController
  StreamController _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是我的实体类
    _streamController = StreamController.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: [
            .....省略无关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(
                        stream: _streamController.stream,//数据流
                        initialData: focusItemModel,//初始值
                        builder: (BuildContext context,
                            AsyncSnapshot 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 理解的也不是很深刻,没有往太细节去讲解,只是结合自己的项目去讲解了开发中遇到的问题,希望大家提提意见,共同进步。

你可能感兴趣的:(Flutter ——状态管理 | StreamBuild)