本文主要讲解了 Flutter 状态管理框架 Provider 的发展过程、设计目的以及如何使用,保证读过之后可以快速上手 Provider。
InheritedWidget
InheritedWidget 是 Flutter 提供可以在 WidgetTree 上进行数据共享的 Widget,它解决了依赖注入的问题。 如果不使用它,subWidget 如果想获取 parentWidget 的数据就需要从 parentWidget 创建 subWidget 的时候一层层的传递 Data:
通过构造注入的方式会导致代码难以维护,所以我们可以在 WidgetTree 某个合适的位置放置一个 InheritedWidget 用来管理全部数据,它的 subWidget 都可以通过 InheritedWidget 来获取数据:
自定义 InheritedWidget
使用 InheritedWidget 可以分为三个步骤:
- 继承 InheratedWidget
- 为实现类添加 data field
- 添加 of 方法让 child 可以方便的获取 InheritedWidget
class CounterInheritedWidget extends InheritedWidget {
final int count;
CounterInheritedWidget({this.count, Widget child})
: super(child: child);
static CounterInheritedWidget of(BuildContext context) {
/// 通过 BuildContext 可以在 WigetTree 上找到最近的 CounterInheritedWidget
return context.inheritFromWidgetOfExactType(CounterInheritedWidget);
}
@override
bool updateShouldNotify(CounterInheritedWidget oldWidget) {
return count != oldWidget.count;
}
}
SubWiget 获取数据
class MyHomePage extends StatefulWidget {
...
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
...
@override
Widget build(BuildContext context) {
return CountInheritedWidget(
count:0,
child: SubChildWidget()
);
}
}
class SubChildWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Text("value:${CountInheritedWidget.of(context).count}");
}
}
InheritedWidget 只提供了数据共享的功能,没有提供 Widget 之间通信的功能,比如点击一个按钮使 count++ 后刷新界面,所以 Fultter 又提供了 ChangeNotifier,ChangeNotifier 配合 InheritedWidget 可以做到组件间通信。
ChangeNotifier
ChangeNotifier 使用了观察者模式,可以向 ChangeNotifier 添加 Listener,在数据变化的时候 ChangeNotifier 会通知它的观察者。这里我们使用 ValueNotifier(ChangeNotifier 的一种子类)来展示如何和 InheritedWidget 配合实现通信。 原理其实很简单,InheritedWidget 可以将它的内容分享给子树,那么我们在 InheritedWidget 声明一个 ChangeNotifier 让它的子树自由访问就好了.
为 InheritedWidget 添加 ValueNotifier
class CountInheritedWidget extends InheritedWidget {
int count = 1;
final ValueNotifier<int> countNotifier;
CountInheritedWidget(this.countNotifier, Widget child) : super(child: child);
@override
bool updateShouldNotify(CountInheritedWidget oldWidget) {
return count != oldWidget.count;
}
void increase() {
count++;
countNotifier.value = count;
}
static CountInheritedWidget of(BuildContext context) {
// ignore: deprecated_member_use
return context.inheritFromWidgetOfExactType(CountInheritedWidget);
}
}
SubWidget 使用 ChangeNotifier
class CounterInheritedPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return CounterInheritedPageState();
}
}
class CounterInheritedPageState extends State {
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
/// 添加监听
CountInheritedWidget.of(context).countNotifier.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("count:${CountInheritedWidget.of(context).count}"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
/// 改变 value
CountInheritedWidget.of(context).increase();
},
),
);
}
}
为了不显示的 addListener,我们也可以使用 ValueListenableBuilder:
class SubChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: CountInheritedWidget.of(context).countNotifier,
builder: (context, value, _) {
return Scaffold(
body: Center(
child: Text("count:$value"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
CountInheritedWidget.of(context).increase();
},
),
);
},
);
}
}
ChangeNotifierProvider
为了应对更加复杂的业务场景,开源社区基于 InheritedWidget 封装了更加强大的 Provider 框架,Provider 中的 ChangeNotifierProvider 配合 ChangeNotifier 可以进行复杂的状态管理。
作用
官网上有这一段话:
ChangeNotifierProvider is the widget that provides an instance of a ChangeNotifier to its descendants.
意思是 ChangeNotifierProvider 可以向它的子类提供 ChangeNotifier 的实例,这和我们上面的代码是不是很类似。
使用
先定义一个 ChangeNotifier:
class CounterModel with ChangeNotifier{
int count = 10;
void increase(){
count++;
notifyListeners();
}
}
然后在根 Widget 下添加监听:
void main() => runApp(MyApp());
...
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => CounterModel(),
child: CounterProviderSimplePage());
}
}
最后在 SubWidget 下通过 Provider.of 来获取 CounterModel 进行数据的获取和修改:
class CounterProviderSimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("counter1: ${Provider.of<CounterModel>(context).count}")
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).increase();
},
),
);
}
}
子类获取数据的两种方式
Provider.of
第一种方式就是上面的代码采用的 Provider 的 of 静态方法,通过设置的类型 T,Provider 会使用 BuilderContext 找到最近的父类 T 从而获取到数据。
Consumer
第二种方式就是通过 Consumer:
class CounterProviderSimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context,counterModel,_){
return Container(
child: Center(
child: Text("counter: ${counterModel.count}"),
),
);
},
);
}
}
Consumer 需要传入一个 builder:
/// context: 上下文
/// value:所监听的 ChangeNotifier 类型
/// child:不跟随 T 变化而重新 build 的 child Widget
final Widget Function(BuildContext context, T value, Widget child) builder;
两种方式的区别
其实两种方式区别不大,因为 Consumer 内部也是采用的 Provider.of :
class Consumer<T> extends SingleChildStatelessWidget {
...
final Widget Function(BuildContext context, T value, Widget child) builder;
@override
Widget buildWithChild(BuildContext context, Widget child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
之所以提供 Consumer 是为了让粒度更细从而提高性能,比如:
@override
Widget build(BuildContext context) {
return FooWidget(
child: BarWidget(
bar: Provider.of<Bar>(context),
),
);
}
我们只想刷新 BarWidget,但是会造成 FooWidget 不必要的刷新,接下来我们使用 Consumer 来细化粒度:
@override
Widget build(BuildContext context) {
return FooWidget(
child: Consumer<Bar>(
builder: (_, bar, __) => BarWidget(bar: bar),
),
);
}
这样就不会造成 FooWidget 不必要的刷新了。
MultiProvider
当一个复杂的页面有个多个数据源的时候,为了提高刷新效率,我们可能设置多个 ChangeNotifier,这时候就需要用到 MultiProvider:
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<RedCounterModel>(
create: (_) => RedCounterModel(),
),
ChangeNotifierProvider<BlueCounterModel>(
create: (_) => BlueCounterModel(),
),
],
child: CounterProviderSimplePage()
);
}
}
MultiProvider 只是让代码更加易读而已,上面的代码等价于:
Widget build(BuildContext context) {
return ChangeNotifierProvider<RedCounterModel>(
create: (_) => RedCounterModel(),
child: ChangeNotifierProvider<BlueCounterModel>(
create: (_) => BlueCounterModel(),
child: CounterProviderSimplePage(),
),
);
}
所以 ChangeNotifierProvider 其实是根据我们声明的顺序嵌套了,这一点可以在 FlutterInspect 中验证:
Selector
我们可以通过多个 ChangeNotifier + Consumer 来细分粒度,提高刷新性能,但是多个 ChangeNotifier 会有嵌套问题,这个时候如果想用一个 ChangeNotifier 来控制不同区域进行刷新就可以使用 Selector,两者的区别如下图:
Non-Selector DoubleCounter
- Model
class DoubleCounterModel with ChangeNotifier {
var countRed = 0;
var countBlue = 0;
void increaseRed() {
countRed++;
notifyListeners();
}
void increaseBlue() {
countBlue++;
notifyListeners();
}
}
DoubleCounterModel 的 countRed 用来控制 RedCounter, countBlue 用来控制 BlueCounter。
- Widget 然后声明 RedCounter 和 BlueCounter
class RedCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<DoubleCounterModel>(
builder: (context, counterModel, child) {
print("red counter build");
return Row(
children: <Widget>[
Container(
height: 50,
width: 200,
color: Colors.red,
child: Center(child: Text("counter: ${counterModel.countRed}")),
),
child
],
);
},
child: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
Provider.of<DoubleCounterModel>(context, listen: false).increaseRed();
},
child: Icon(Icons.add),
),
);
}
}
class BlueCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<DoubleCounterModel>(
builder: (context, counterModel, child) {
print("blue counter build");
return Row(
children: <Widget>[
Container(
height: 50,
width: 200,
color: Colors.blue,
child: Center(child: Text("counter: ${counterModel.countBlue}")),
),
child
],
);
},
child: FloatingActionButton(
backgroundColor: Colors.blue,
onPressed: () {
Provider.of<DoubleCounterModel>(context, listen: false).increaseBlue();
},
child: Icon(Icons.add),
),
);
}
}
- 使用
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
...
}
class MyHomePage extends StatefulWidget {
...
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return MultiProvider(providers: [
ChangeNotifierProvider<DoubleCounterModel>(
create: (_) => DoubleCounterModel())
], child: DoubleCounterPage());
}
}
class DoubleCounterPage extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RedCounter(),
BlueCounter()
],
),
);
}
}
- 效果 此时我们发现虽然只修改了 BlueCounter 但是 RedCounter 也会重新 build,这不是最佳的刷新策略,那么让我们来看一下如何用 Seletor 提高刷新效率
Selector
Selector 的构造主要包含以下四个参数:
Selector<A,S>(
selector: S Function(BuildContext, A),
shouldRebuild: bool Function(T previous, T next),
builder: Widget Function(BuildContext context, T value, Widget child)
child:Widget
)
- A 代表传入的数据源,比如 DoubleCounterModel, S 代表想要监听的 A 的某个属性,比如 DoubleCounterModel 的 countRed
- builder 和 child 的用法同 Consumer 一样
- selector 负责从 A 中筛选出我们需要监听的 S,然后将 S 传给 builder 进行刷新
- shouldRebuild 用来覆盖默认的对比算法,可以不设置 比较数据是否发生改变的源码如下:
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
T value;
Widget cache;
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
final selected = widget.selector(context);
var shouldInvalidateCache = oldWidget != widget ||
/// 如果 shouldRebuild 不为空就是用 shouldRebuild
(widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected)) ||
/// 如果 shouldRebuild 就使用默认的方法比较
(widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected));
/// 如果数据发生了改变就 rebuild
if (shouldInvalidateCache) {
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
/// 如果数据没有发生改变就返回旧的 widget
return cache;
}
}
Selector DoubleCounter
明白了 Selector 的定义使用起来就非常简单,只需要通过 selector 方法筛选出感兴趣的数据即可:
- 声明 Selector
class RedCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<DoubleCounterModel, int>(
builder: (context, count, child) {
print("red counter build");
return Row(
children: <Widget>[
Container(
height: 50,
width: 200,
color: Colors.red,
child: Center(child: Text("counter: $count")),
),
child
],
);
},
child: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
Provider.of<DoubleCounterModel>(context, listen: false).increaseRed();
},
child: Icon(Icons.add),
),
selector: (context, counterModel) {
return counterModel.countRed;
},
);
}
}
class BlueCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<DoubleCounterModel, int>(
builder: (context, count, child) {
print("blue counter build");
return Row(
children: <Widget>[
Container(
height: 50,
width: 200,
color: Colors.blue,
child: Center(child: Text("counter: $count}")),
),
child
],
);
},
child: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
Provider.of<DoubleCounterModel>(context, listen: false).increaseBlue();
},
child: Icon(Icons.add),
),
selector: (context, counterModel) {
return counterModel.countBlue;
},
);
}
}
- 效果 此时再看下效果就可以发现两个 Widget 不再相互影响了:
更轻量级的 select
Provider 在 4.0.0 添加了和 Consumer 同等级的 Selector,又在 4.1.0 添加了 select 方法,它作用类似于 Selector,但是更加轻量级。 可以参考Provider 的 changeLog。 With Selector:
Widget build(BuildContext context) {
return Selector<Person, String>(
selector: (_, p) => p.name,
builder: (_, name, __) {
return Text(name);
},
),
}
VS with the new select extension:
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
需要注意的是 4.10.0 要求 Flutter SDK 的版本>=1.15.17。
为项目添加 Provider
在 pubspec.yaml 添加依赖即可:
dependencies:
flutter:
sdk: flutter
provider: ^4.0.0
多类型的 Consumer/Selector
上面所讲的 Consumer,Selector<A,S> 都只能监听一种类型的数据,官方提供了可以监听多种类型数据的类,比如:
- Consumer2<A, B>,Consumer3<A, B, C> ...
- Selector2<A, B, S>,Selector3<A, B, C, S> ...
其他
官方文档上还有很多规范,比如 ChangeNotifierProvider 添加 notifier 的两种方式:
/// 添加新的 Model 使用 create
ChangeNotifierProvider(
create: (_) => MyModel(),
child: ...
)
/// 添加已经存在的 Model 使用 value
MyModel model = MyModel();
ChangeNotifierProvider.value(
value: model,
child: ...
)
其他注意点请参考 github.com/rrousselGit…