不得不说,掘金的文章目录功能真的很方便。
Flutter,或者说app,数据传递,是难以避免的事情。页面内部之间,页面与页面之间,张三和李四,两者懂事需要说一些悄悄话。
这件事,我们有时叫 数据传递,有时候叫做状态管理,这随便啦。反正就是要传递下数据。
Flutter的状态管理,分为2种:
- 1、局部状态
- 2、全局状态
局部状态:一个StatefulWidget内部之间的事情,用setState解决,这点就不展开说了。
全局状态:整个app很多页面都需要用到的状态,比如是否登录了,用户名、用户id等。全局状态的管理的方式有好几种,比如 :
`InheritedWidget`
`StreamBuilder`,
`ValueListenableBuilder`
另外还有有一些依赖库,比如google官方推荐的`Provider`
利用 InheritedWidget ,我们可以实现 从上向下 的数据传递。利用 Notification 我们可以实现 从下向上 的数据传递利用 ValueListenableBuilder 实现,与方向无关,只要数据源发生变化它就会重新构建子组件树,因此可以实现任意流向的数据共享,
但是很多时候,我们需要异步UI更新,这就需要用到 FutureBuilder、StreamBuilder
一、InheritedWidget 从上向下 传递
(只能从上向下,从下往上一般用的是:Notification)
🌰栗子来了,比如官方的主题管理和Local语言环境,用的是InheritedWidget来实现的。这个是不是很“从上往下”。(是的,自问自答,自投自抢)
inherited 这个单词本身,就有 ’继承‘ 和 ’遗传‘ 的意思。
一、1、 InheritedWidget 要义
- 1、 使用 InheritedWidget 作为全局状态的管理者,那么将InheritedWidget作为根Widget可以实现下面的Widget都可以获取到该Widget持有的状态。
- 2、我们在应用的根 widget 中通过
InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!
看看InheritedWidget 类
这个可以先忽略,回头在看
/// 抽象类,继承自Proxywidget 继承路径InheritedWidget => ProxyWidget => Widget
abstract class InheritedWidget extends ProxyWidget {
/// 构造函数
/// 因为InheritedWidget是没有界面的Widget,所有需要传入实际的Widget
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
/// 重写了超类Widget createElement方法
@override
InheritedElement createElement() => InheritedElement(this);
/// 父级或祖先widget中改变(updateShouldNotify返回true)时会被调用。
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
一.2、 来个例子
功能:登录小例子,页面之间,根据是否登录,做出对应的操作
.
.
分解1 继承自InheritedWidget,构建of,复写 updateShouldNotify
of方法: 定义一个便捷方法,方便子树中的widget获取共享数据updateShouldNotify方法:该回调决定当data发生变化时,是否通知子树中依赖data的Widget
import 'package:flutter/material.dart';
class LoginState {
bool isLogin = false;
bool operator ==(other) {
return isLogin == (other as LoginState).isLogin;
}
}
/*
InheritedWidget持有LoginState对象;
另外需要提供of静态方法,方法实现是context.inheritedFromWidgetOfExactType;
updateShouldNotify方法是用来判断当该Widget变化时,
这里面如果LoginState变化了,即isLogin参数变化了
才会去通知那些依赖该Widget的Widget变化。
*/
class LoginStateWidget extends InheritedWidget {
final LoginState loginState;
const LoginStateWidget(
{Key? key, required this.loginState, required Widget child})
: super(key: key, child: child);
//定义一个便捷方法,方便子树中的widget获取共享数据
static LoginStateWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType(aspect: LoginStateWidget);
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(LoginStateWidget oldWidget) {
return oldWidget.loginState == this.loginState;
}
}
.
.
分解2、 InheritedWidget 作为 根Widght,包裹 MaterialApp 这很重要!
LoginStateWidget(继承自InheritedWidget) 作为 根Widght,并且传入了一个初始的LoginState。
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return LoginStateWidget(
loginState: LoginState(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: new Text('Inherited测试'),
),
body: MainPage(),
),
));
}
}
分解3 添加两个页面,一个MainPage,一个LoginPage
核心操作:build里面,通过的LoginStateWidget的静态 of 方法 拿到共享数据 LoginState loginState = LoginStateWidget.of(context)!.loginState;
MainPage 页面有两个按钮,模拟登录成功,按下不同按钮,都会修改共享得到的值。
Login页面会根据被MainPage页面修改的值,然后显示不同的文字
// 假设app默认进入主页
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 关键一步,通过的LoginStateWidget的静态 of 方法 拿到共享数据
LoginState loginState = LoginStateWidget.of(context)!.loginState;
print('MainPage loginState isLogin 值:${loginState.isLogin}');
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('MainPage 页', textScaleFactor: 3),
MaterialButton(
onPressed: () {
loginState.isLogin = true;
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('模拟正确登录'),
),
MaterialButton(
onPressed: () {
loginState.isLogin = false;
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('模拟错误登录'),
),
MaterialButton(
onPressed: () {
showDialog<String>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('结果'),
children: <Widget>[
SimpleDialogOption(
child: Text('当前isLogin为: ${loginState.isLogin}'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('当前的jsLogin的值得'),
)
],
),
),
);
}
}
// 假设在登录页之后,会显示登录之后结果,根据登录成功或者失败,显示对应的按钮
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 这个子Wight也通过 LoginStateWidget 的of方法拿到 共享数据
LoginState loginState = LoginStateWidget.of(context)!.loginState;
print('LoginPage loginState 值:${loginState.isLogin}');
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Container(
color: Colors.grey,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('LoginPage 页', textScaleFactor: 3),
MaterialButton(
onPressed: () {
print('LoginPage pop之前:${loginState.isLogin}');
loginState.isLogin = !loginState.isLogin;
Navigator.pop(context);
print('LoginPage pop之后:${loginState.isLogin}');
},
child: Text(loginState.isLogin ? '退出登录' : '登录'),
)
],
),
),
),
),
);
}
}
.
.
来份完整的代码吧。
按道理这些Page应该写在不同的文件好一些,但是这里为了方便展示,先到一块去了
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LoginStateWidget(
loginState: LoginState(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: new Text('Inherited测试'),
),
body: MainPage(),
),
));
}
}
class LoginState {
bool isLogin = false;
bool operator ==(other) {
return isLogin == (other as LoginState).isLogin;
}
}
/*
InheritedWidget持有LoginState对象;
另外需要提供of静态方法,方法实现是context.inheritedFromWidgetOfExactType;
updateShouldNotify方法是用来判断当该Widget变化时,
这里面如果LoginState变化了,即isLogin参数变化了
才会去通知那些依赖该Widget的Widget变化。
*/
class LoginStateWidget extends InheritedWidget {
final LoginState loginState;
const LoginStateWidget(
{Key? key, required this.loginState, required Widget child})
: super(key: key, child: child);
//定义一个便捷方法,方便子树中的widget获取共享数据
static LoginStateWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType(aspect: LoginStateWidget);
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(LoginStateWidget oldWidget) {
return oldWidget.loginState == this.loginState;
}
}
// 假设app默认进入主页
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 关键一步,通过的LoginStateWidget的静态 of 方法 拿到共享数据
LoginState loginState = LoginStateWidget.of(context)!.loginState;
print('MainPage loginState isLogin 值:${loginState.isLogin}');
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('MainPage 页', textScaleFactor: 3),
MaterialButton(
onPressed: () {
loginState.isLogin = true;
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('模拟正确登录'),
),
MaterialButton(
onPressed: () {
loginState.isLogin = false;
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('模拟错误登录'),
),
MaterialButton(
onPressed: () {
showDialog<String>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('结果'),
children: <Widget>[
SimpleDialogOption(
child: Text('当前isLogin为: ${loginState.isLogin}'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
// 从 loginState 中得到 isLogin 显示不同的文字
child: Text('当前的jsLogin的值得'),
)
],
),
),
);
}
}
// 假设在登录页之后,会显示登录之后结果,根据登录成功或者失败,显示对应的按钮
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 这个子Wight也通过 LoginStateWidget 的of方法拿到 共享数据
LoginState loginState = LoginStateWidget.of(context)!.loginState;
print('LoginPage loginState 值:${loginState.isLogin}');
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Container(
color: Colors.grey,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('LoginPage 页', textScaleFactor: 3),
MaterialButton(
onPressed: () {
print('LoginPage pop之前:${loginState.isLogin}');
loginState.isLogin = !loginState.isLogin;
Navigator.pop(context);
print('LoginPage pop之后:${loginState.isLogin}');
},
child: Text(loginState.isLogin ? '退出登录' : '登录'),
)
],
),
),
),
),
);
}
}
效果:
按下 模拟正确登录
- 按下: “模拟正确登录”
- 按下 “退出登录” 把isLogin位置为false
- 测试状态显示为 false
.
.
按下 模拟错误登录
- 按下: “模拟错误登录”
- 按下 “登录” 把isLogin位置为true
- 测试状态显示为 true
应该非常清晰明了吧。
-
MainPage通过共享数据改变了isLogin的状态
-
LoginPage按下不同的按钮,也通过共享数据改变了isLogin的状态
.
.
一.3、InheritedWidget 和 didChangeDependencies
-
在之前介绍
StatefulWidget时,我们提到State对象有一个didChangeDependencies回调,它会在“依赖”发生变化时被Flutter 框架调用。而这个“依赖”指的就是子 widget 是否使用了父 widget 中InheritedWidget的数据!如果使用了,则代表子 widget 有依赖;如果没有使用则代表没有依赖。 -
也就是说,只有当state的build方法获取了共享数据值(通过 dependOnInheritedElement 的方式),子widget的 didChangeDependencies 方法才会调用,这个我们重新 didChangeDependencies 输出个日志尝试一下就知道
-
这种机制可以使子组件在所依赖的
InheritedWidget变化时来更新自身!比如当主题、locale(语言)等发生变化时,依赖其的子 widget 的didChangeDependencies方法将会被调用。
一.4、如何判断Widget是否为 InheritedWidget 的子Widget呢?
- 就看是否调用了
dependOnInheritedWidgetOfExactType()方法
其实真正注册依赖关系的方法是 dependOnInheritedElement ,只不过 dependOnInheritedWidgetOfExactType 又调用了 dependOnInheritedElement。
二、InheritedWidget 的缺点
一个InheritedWidget,如果存在多个子节点,有时候我们只想改变一个子节点。可是当某个子节点setSate的时候,很容易造成全局build。这是一种资源浪费。
比较好的解决办法是使用缓存,而Google的 Provider 库就考虑到了这个问题。
其实很多三方数据共享库也解决了这个问题,但是不管是官方还是三方,本质上,基本都是对 InheritedWidget 的封装。
看源码
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
一.5、如何只使用共享数据,但是不调用 didChangeDependencies 方法呢?
我们只需要将ShareDataWidget.of()的实现改一下即可
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget of(BuildContext context) {
//return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;
}
- 也就是把
dependOnInheritedWidgetOfExactType改成getElementForInheritedWidgetOfExactType
我们可以看到,dependOnInheritedWidgetOfExactType() 比 getElementForInheritedWidgetOfExactType()多调了dependOnInheritedElement方法,dependOnInheritedElement
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}
@override
InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
//多出的部分
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
一.6、 手动写一个 Provider
Provider是Google的一个三方库,做状态管理的。
日和使用网上有大量的文章。
我们可以通过自己简单写一个,来明白他的原理。
这里有个文章,还是相当不错的。
跨组件状态共享(Provider) book.flutterchina.club/chapter7/pr…
三、Notification 从下向上传递
我们说了 InheritedWidget 是 从上向下 的,而 从下向上, 一般用的是 Notification。
子节点状态变更,向上上报通过发送通知的方式
- 定义通知类,继承自Notification
- 父节点使用NotificationListener进行监听捕获通知
- 子节点有数据变更,调用接口进行上报Notification(data).dispatch(context)
来个例子
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: new Text('Notify测试'),
),
body: NotificationRoute(),
),
);
}
}
// 定义通知类,继承自Notification
class MyNotification extends Notification {
MyNotification(this.msg);
final String msg;
}
class NotificationRoute extends StatefulWidget {
@override
NotificationRouteState createState() {
return NotificationRouteState();
}
}
class NotificationRouteState extends State<NotificationRoute> {
String _msg="";
@override
Widget build(BuildContext context) {
//父节点使用NotificationListener进行监听捕获通知
return NotificationListener<MyNotification>(
onNotification: (notification) {
setState(() {
_msg+=notification.msg+" ";
});
return true;
},
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// ElevatedButton(
// onPressed: () => MyNotification("Hi").dispatch(context),
// child: Text("Send Notification"),
// ),
Builder(
builder: (context) {
return ElevatedButton(
//按钮点击时分发通知
onPressed: () {
// 发出通知,进行分发
MyNotification("Hi").dispatch(context);
},
child: Text("Send Notification"),
);
},
),
Text(_msg)
],
),
),
);
}
}
.
.
效果 按下按钮,添加多一个 Hi
所谓冒泡
自下向上层层分发消息传递
监听嵌套和 阻止冒泡
如果有多个监听,正常情况下(onNotification中返回true),会一层一层往外传,多个监听都收到。这个是正常的冒泡
那么,我们可不可以阻止冒泡呢?
答案是可以的。
其实也就是 onNotification 返回 false,表示阻止冒泡,不往外分发了。自己消费掉了。
class NotificationRouteState extends State<NotificationRoute> {
String _msg="";
@override
Widget build(BuildContext context) {
//监听通知
return NotificationListener<MyNotification>(
onNotification: (notification){
print(notification.msg); //打印通知
return false;
},
child: NotificationListener<MyNotification>(
onNotification: (notification) {
setState(() {
_msg+=notification.msg+" ";
});
return false;
},
child: ...//省略重复代码
),
);
}
}
上列中两个
NotificationListener进行了嵌套,子NotificationListener的onNotification回调返回了false,表示不阻止冒泡,所以父NotificationListener仍然会受到通知,所以控制台会打印出通知信息;如果将子NotificationListener的onNotification回调的返回值改为true,则父NotificationListener便不会再打印通知了,因为子NotificationListener已经终止通知冒泡了。
四、ValueListenableBuilder
InheritedWidget 提供一种在 widget 树中从上到下共享数据的方式,但是也有很多场景数据流向并非从上到下,比如从下到上或者横向等。为了解决这个问题,Flutter 提供了一个 ValueListenableBuilder 组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行其 builder,定义如下:
const ValueListenableBuilder({
Key? key,
required this.valueListenable, // 数据源,类型为ValueListenable<T>
required this.builder, // builder
this.child,
}
- valueListenable:类型为
ValueListenable<T>,表示一个可监听的数据源。 - builder:数据源发生变化通知时,会重新调用 builder 重新 build 子组件树。
- child: builder 中每次都会重新构建整个子组件树,如果子组件树中有一些不变的部分,可以传递给child,child 会作为builder的第三个参数传递给 builder,通过这种方式就可以实现组件缓存,原理和AnimatedBuilder 第三个 child 相同。
可以发现 ValueListenableBuilder 和数据流向是无关的,只要数据源发生变化它就会重新构建子组件树,因此可以实现任意流向的数据共享。
来个例子
一个计数器
class ValueListenableRoute extends StatefulWidget {
const ValueListenableRoute({Key? key}) : super(key: key);
@override
State<ValueListenableRoute> createState() => _ValueListenableState();
}
class _ValueListenableState extends State<ValueListenableRoute> {
// 定义一个ValueNotifier,当数字变化时会通知 ValueListenableBuilder
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
static const double textScaleFactor = 1.5;
@override
Widget build(BuildContext context) {
// 添加 + 按钮不会触发整个 ValueListenableRoute 组件的 build
print('build');
return Scaffold(
appBar: AppBar(title: Text('ValueListenableBuilder 测试')),
body: Center(
child: ValueListenableBuilder<int>(
builder: (BuildContext context, int value, Widget? child) {
// builder 方法只会在 _counter 变化时被调用
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
child!,
Text('$value 次',textScaleFactor: textScaleFactor),
],
);
},
valueListenable: _counter,
// 当子组件不依赖变化的数据,且子组件收件开销比较大时,指定 child 属性来缓存子组件非常有用
child: const Text('点击了 ', textScaleFactor: textScaleFactor),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
// 点击后值 +1,触发 ValueListenableBuilder 重新构建
onPressed: () => _counter.value += 1,
),
);
}
}
运行后连续点击两次 + 按钮效果如下:
可以看见,功能正常实现了,同时控制台只在页面打开时 build 了一次,点击 + 按钮的时候只是ValueListenableBuilder 重新构建了子组件树,而整个页面并没有重新 build ,因此日志面板只打印了一次 "build" 。因此我们有一个建议就是:尽可能让 ValueListenableBuilder 只构建依赖数据源的widget,这样的话可以缩小重新构建的范围,也就是说 ValueListenableBuilder 的拆分粒度应该尽可能细。
关于 ValueListenableBuilder 有两点需要了解:
- 和数据流向无关,可以实现任意流向的数据共享。
- 实践中,ValueListenableBuilder 的拆分粒度应该尽可能细,可以提高性能。
五、异步更新UI FutureBuilder和StreamBuilder
很多时候我们会依赖一些异步数据来动态更新UI,比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面;又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。当然,通过 StatefulWidget 我们完全可以实现上述这些功能。但由于在实际开发中依赖异步数据更新UI的这种场景非常常见,因此Flutter专门提供了FutureBuilder和StreamBuilder两个组件来快速实现这种功能。
五.1、FutureBuilder
FutureBuilder会依赖一个Future,通常在Future里面做异步耗时操作,在 builder 里面更新UI。
FutureBuilder构造函数
FutureBuilder({
this.future,
this.initialData,
required this.builder,
})
future:FutureBuilder依赖的Future,通常是一个异步耗时任务。initialData:初始数据,用户设置默认数据。builder:Widget构建器;该构建器会在Future执行的不同阶段被多次调用,构建器签名如下
Function (BuildContext context, AsyncSnapshot snapshot)
snapshot会包含当前异步任务的状态信息及结果信息 ,比如我们可以通过snapshot.connectionState获取异步任务的状态信息、通过snapshot.hasError判断异步任务是否有错误等等,完整的定义读者可以查看AsyncSnapshot类定义。
另外,FutureBuilder的builder函数签名和StreamBuilder的builder是相同的。
例子
我们实现一个路由,当该路由打开时我们从网上获取数据,获取数据时弹一个加载框;获取结束时,如果成功则显示获取到的数据,如果失败则显示错误。
Future<String> mockNetworkData() async {
return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");
}
FutureBuilder使用代码如下:
...
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<String>(
future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// 请求已结束
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
// 请求失败,显示错误
return Text("Error: ${snapshot.error}");
} else {
// 请求成功,显示数据
return Text("Contents: ${snapshot.data}");
}
} else {
// 请求未结束,显示loading
return CircularProgressIndicator();
}
},
),
);
}
注意:示例的代码中,每次组件重新build 都会重新发起请求,因为每次的 future 都是新的,实践中我们通常会有一些缓存策略,常见的处理方式是在 future 成功后将 future 缓存,这样下次build时,就不会再重新发起异步任务。
上面代码中我们在builder中根据当前异步任务状态ConnectionState来返回不同的widget。ConnectionState是一个枚举类,定义如下:
enum ConnectionState {
/// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
none,
/// 异步任务处于等待状态
waiting,
/// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态。
active,
/// 异步任务已经终止.
done,
}
注意,ConnectionState.active只在StreamBuilder中才会出现
五.2、 StreamBuilder
我们知道,在Dart中Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件。下面看一下StreamBuilder的默认构造函数:
StreamBuilder({
this.initialData,
Stream<T> stream,
required this.builder,
})
可以看到和FutureBuilder的构造函数只有一点不同:前者需要一个future,而后者需要一个stream。
示例
我们创建一个计时器的示例:每隔1秒,计数加1。这里,我们使用Stream来实现每隔一秒生成一个数字:
Stream<int> counter() {
return Stream.periodic(Duration(seconds: 1), (i) {
return i;
});
}
StreamBuilder使用代码如下
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counter(), //
//initialData: ,// a Stream<int> or null
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('没有Stream');
case ConnectionState.waiting:
return Text('等待数据...');
case ConnectionState.active:
return Text('active: ${snapshot.data}');
case ConnectionState.done:
return Text('Stream 已关闭');
}
return null; // unreachable
},
);
}
注意,本示例只是为了演示StreamBuilder的使用,在实战中,凡是UI会依赖多个异步数据而发生变化的场景都可以使用StreamBuilder。
就写到这里吧。
参考:
Flutter状态管理(1)——InheritedWidget
cloud.tencent.com/developer/a…
Flutter 数据传输 cloud.tencent.com/developer/a…
Flutter状态管理(2)——单Stream和广播Stream
cloud.tencent.com/developer/a…
通知 Notification
book.flutterchina.club/chapter8/no…
Flutter状态管理(2)——单Stream和广播Stream
cloud.tencent.com/developer/a…
异步UI更新(FutureBuilder、StreamBuilder) book.flutterchina.club/chapter7/fu…