关于Flutter中的状态管理,可以参考官网的介绍:Simple app state management
中文网的介绍:简单的应用状态管理
Flutter 官方的两个sample:
provider_counter
provider_shopper
ChangeNotifierProvider的使用
0. 添加provider包的依赖
在pubspec.yaml中添加provider:
dependencies:
flutter:
sdk: flutter
# Import the provider package.
provider: ^3.0.0
provider包的文档:
https://pub.dev/documentation/provider/latest/
里面介绍来从2.0版本到3.0版本的迁移修改方法,
在3.0版本中,有以下Provider类型:
后续博文将逐个介绍学习各Provider的使用场景
1. 定义一个数据Model类,继承自ChangeNotifier
/// Simplest possible model, with just one field.
///
/// [ChangeNotifier] is a class in `flutter:foundation`. [Counter] does
/// _not_ depend on Provider.
class Counter with ChangeNotifier {
int value = 0;
void increment() {
value += 1;
notifyListeners();
}
}
这里的ChangeNotifier其实很简单,类似于一个Observable,继承自Listenable,内部维护了一个
ObserverList <VoidCallback> _listeners,提供添加和删除listener的方法,有一个notify方法,用来通知所有的观察者(或者称为消费者Consumer)
大致源码如下:
class ChangeNotifier implements Listenable {
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
//省略addListener、removeListener等方法
@protected
@visibleForTesting
void notifyListeners() {
assert(_debugAssertNotDisposed());
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'foundation library',
context: ErrorDescription('while dispatching notifications for $runtimeType'),
informationCollector: () sync* {
yield DiagnosticsProperty<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
}
2. 使用ChangeNotifierProvider来将Model与Widget相关联
这里的ChangeNotifierProvider包裹的Widget选哪个要注意一下:
⚠️注意:ChangeNotifierProvider 放在什么位置:在需要访问它的 widget 之上,但是不要把ChangeNotifierProvider放的级别太高,因为你不希望破坏整个结构
runApp(
// Provide the model to all widgets within the app. We're using
// ChangeNotifierProvider because that's a simple way to rebuild
// widgets when a model changes. We could also just use
// Provider, but then we would have to listen to Counter ourselves.
//
// Read Provider's docs to learn about all the available providers.
ChangeNotifierProvider(
// Initialize the model in the builder. That way, Provider
// can own Counter's lifecycle, making sure to call `dispose`
// when not needed anymore.
builder: (context) => Counter(),
child: MyApp(),
),
);
请注意我们定义了一个 builder 来创建一个 Model 的实例。ChangeNotifierProvider 非常聪明,它 不会 重复实例化 Model,除非在个别场景下。如果该实例已经不会再被调用,ChangeNotifierProvider 也会自动调用 Model 的 dispose() 方法。
如果你想提供更多状态,可以使用 MultiProvider:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) => CartModel()),
Provider(builder: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
3. 定义监听者Consumer,获取Model的值来更新UI
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
// Consumer looks for an ancestor Provider widget
// and retrieves its model (Counter, in this case).
// Then it uses that model to build widgets, and will trigger
// rebuilds if the model is updated.
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
),
),
],
),
)
Consumer widget 唯一必须的参数就是 builder。当 ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners() 时,所有和 Consumer 相关的 builder 方法都会被调用。)
builder 函数的第二个参数是 ChangeNotifier 的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容。
第三个参数是 child,用于优化目的。如果 Consumer 下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。
⚠️注意:最好能把 Consumer 放在 widget 树尽量低的位置上。你总不希望 UI 上任何一点小变化就全盘重新构建 widget 吧
4. 使用Provider.of来更新数据
除了像上一步中使用Consumer可以获取到当前的model数据之外,还可以使用Provider.of;
但是使用Provider.of,默认使用时,Model中的 notifyListeners() 被调用后,所有这个Model关联的Consumer的build方法都会被调用,也就是会刷新相关联的Widget;
floatingActionButton: FloatingActionButton(
// Provider.of is another way to access the model object held
// by an ancestor Provider. By default, even this listens to
// changes in the model, and rebuilds the whole encompassing widget
// when notified.
//
// By using `listen: false` below, we are disabling that
// behavior. We are only calling a function here, and so we don't care
// about the current value. Without `listen: false`, we'd be rebuilding
// the whole MyHomePage whenever Counter notifies listeners.
onPressed: () =>
Provider.of<Counter>(context, listen: false).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
⚠️注意:虽然我们可以使用 Consumer 来实现这个效果,不过这么实现有点浪费。因为我们让整体框架重构了一个无需重构的 widget。所以这里我们可以使用 Provider.of,并且将 listen 设置为 false。在 build 方法中使用上面的代码,当 notifyListeners 被调用的时候,并不会使 widget 被重构。