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。
要求点击“关注”变为“已关注”
如何去实现的?实现的方法有好多种。
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 理解的也不是很深刻,没有往太细节去讲解,只是结合自己的项目去讲解了开发中遇到的问题,希望大家提提意见,共同进步。