文章目录
状态管理
Flutter
页面是有widget
构成的,在widget
中就有有状态 widget
和无状态 widget
。这里的状态是State
,翻看源码可以看到State
的描述是这样的:(1)在构建widget
时可以同步读取(2)在widget
的生存期内可能会更改。widget
实现者的职责是确保使用State.setState
在状态改变时迅速得到通知。因此状态管理
就是在state
变化时迅速通知到widget
的实现着,让其根据通知做出相应的处理。
上篇文章(Flutter widget 间传递数据的方案 InheritedWidget、Notification、EventBus )我们了解了数据在widget
之间的传递,文中的方案对状态的管理耦合度都比较高。
早就听闻官方推荐的 Provider 库和 阿里出品的 fish-redux 库
通过本篇我们来学习大厂们是如何对state
进行管理的。
Provider
Provider 4.0.5:依赖项注入(DI)和状态管理之间的混合,使用
widgets
构建。
通过使用widgets
进行状态管理,provider
可以保证:
- 可维护性,通过强制的单向数据流实现
- 可测试性/可组合性,因为始终可以模拟/覆盖值
- 健壮性,因为很难忘记处理模型/小部件的更新方案
使用文档
暴露(创建)值
创建新的对象
- 推荐做法:在
create
内创建一个新对象
Provider(
create: (_) => new MyModel(),
child: ...
)
- 不推荐做法:使用
Provider.value
创建对象。
ChangeNotifierProvider.value(
value: new MyModel(),
child: ...
)
不要使用随时间变化的变量来创建对象。原因是在这种情况下,变量更改时
将永远不会更新
您的对象
int count;
Provider(
create: (_) => new MyModel(count),
child: ...
)
如果要将随时间变化的变量传递给对象,请考虑使用ProxyProvider
:
int count;
ProxyProvider0(
update: (_, __) => new MyModel(count),
child: ...
)
使用已存在的对象
如果已经有对象实例并想要公开它,则应使用提供程序的
.value
构造函数。否则,可能会在对象仍在使用时调用其dispose
方法。
- 推荐做法:使用
ChangeNotifierProvider.value
提供一个现有的ChangeNotifier
。
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
- 不推荐做法:使用默认构造函数重用现有的
ChangeNotifier
MyChangeNotifier variable;
ChangeNotifierProvider(
create: (_) => variable,
child: ...
)
读取值
读取值的最简单方法是使用静态方法
Provider.of <T>(BuildContext context)
。该方法从传递BuildContext
的widget
开始,在widget tree
中查找如果找到,它将返回找到的T
类型的最近变量(如果未找到,则抛出该异常)。
结合暴露一个值
的第一个示例,此widget
将读取显示的String
并呈现“ Hello World”
。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// String 类型
Provider.of<String>(context)
);
}
}
另外,也可以不使用Provider.of
,而可以使用Consumer
和Selector
。 这些对于性能优化或难以获得provider
的BuildContext
后代很有用。有关更多信息,请参见FAQ或 Consumer 和 Selector 的文档。
读取多个值 MultiProvider
在大型应用程序中注入许多值时,Provider
可能会迅速嵌套,如下:
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
这种死亡嵌套,看着就让人入魔,所以Provider
也提供了简便的写法:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
ProxyProvider
从
3.0.0
开始,提供了一种新的provider
:ProxyProvider
。ProxyProvider
是一个provider
,它将来自其他provider
的多个值组合到一个新对象中,并将结果发送给Provider
。 然后,只要新provider
之一依赖更新,该新对象就会被更新。
下面的示例使用ProxyProvider
来基于来自另一个provider
的计数器来构建转换。
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
它具有多种变体,例如:
ProxyProvider vs ProxyProvider2 vs ProxyProvider3
,… 类名后面的数字是ProxyProvider
依赖的其他providers
的数量。ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider
,… 它们的工作方式都相似,但是ChangeNotifierProxyProvider
不会将结果发送到Provider
,而是将其值发送到ChangeNotifierProvider
。
使用文档总结
首先Provider
是依赖注入和状态管理和混合物,也就是它能实现这两种功能。依赖注入这里先暂时不用管,状态管理才是文章的重点。
这里的状态管理主要是指数据、UI的管理。
看了以上文档,如果还是懵逼,那么直接看这里的使用步骤吧。
Provider
简单的使用可以按如下步骤来:
- 创建继承自
ChangeNotifier
的数据类 - 创建值,创建新对象通过
create
的方式,复用对象通过ChangeNotifierProvider.value
的方式 - 获取值,可通过
Provider.of(context)
的方式或者Consumer
和Selector
的方式
简单示例使用
官方 simple,实现的效果是:点击 + 号按钮,标题文本和内容文本都会更新显示次数。 为了更好的理解
Provider
的使用,可看下面的代码
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 ChangeNotifierProvider.value 提供一个现有的ChangeNotifier 创建 Provider
return ChangeNotifierProvider.value(
value: Counter(),
child: MaterialApp(
home: const MyHomePage(),
)
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Title()),
body: const Center(child: CounterLabel()),
floatingActionButton: const IncrementCounterButton(),
);
}
}
/// + 号按钮 widget
class IncrementCounterButton extends StatelessWidget {
const IncrementCounterButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
// listen: false 必须设置
Provider.of<Counter>(context,listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
/// 文本内容 widget
class CounterLabel extends StatelessWidget {
const CounterLabel({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
// ignore: deprecated_member_use
style: Theme.of(context).textTheme.display1,
),
],
);
}
}
/// 标题 widget
class Title extends StatelessWidget {
const Title({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Text('Tapped ${counter.count} times');
}
}
/// 1. 数据类
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
使用 Provider 实现主题切换示例
上篇文章写过
EventBus
实现主题切换 https://blog.csdn.net/ITxiaodong/article/details/105083216,这次使用Provider
也实现主题切换的功能。
完成下面几个步骤即可实现:
- 定义类
extends
或者with ChangeNotifier
,如:点击查看 theme.dart 的实现 - 封装
Store
类,用于所有provider
数据的初始化。如:点击查看 store.dart 的实现 - 在
main.dart
文件中声明为全局的对象,并通过Consumer
去调用。如:点击查看 main.dart 的实现。 - 在
我的页面
点击修改主题后出现弹窗并选择颜色。如:点击查看 mine_page.dart 的实现
为了阅读体验,不粘贴代码了。
具体源码请跳转项目查看:https://github.com/YGragon/Flutter-WanAndroid
参考
- https://pub.dev/packages/provider#-readme-tab-
- https://juejin.im/post/5d00a84fe51d455a2f22023f
- https://juejin.im/post/5d0634c7f265da1b91639232
上车
佛系原创号主