简单翻译来说,Provider:依赖注入与状态管理的结合,并且对外提供Widget使用。使用widget代替纯Dart对象,比如说Stream。因为widget简单,强大且可扩展。使用Provider,可以保证:可维护性(强制的单向数据流),可测试性,健壮性等等。
复制代码
什么是状态?什么是状态管理?
如果说页面是静态,拿到数据(状态),渲染完就完事了。压根就不需要状态管理。
不幸的是,页面不可能全是静态。页面需要响应数据(状态)变化(网络数据?用户操作产生的数据?)更新UI。
同时,数据变化的影响,不仅仅是组件内,还有可能是页面内其他组件,甚至于应用内其他页面。
UI=f(state)。从状态变化到UI更新的过程,我们称之为状态管理。状态管理要尽可能的把这个过程独立出来,让动态界面如同静态页面一般简单。
有哪些状态管理的方式/库
- setState
- FutureBuilder/StreamBuilder/BLoc
- Provider/ScopedModel
- redux/Fish-redux
复制代码
简易版Provider实现
有了状态管理的介绍,我们可以参考Provider,通过手上现有的组件,实现一个简易版的Provider。要用到的系统组件:
setState:StatefulWidget现成的,刷新UI的办法
InheritedWidget:一个基础widget,让widget树具备从上而下传递数据的能力。同时数据变化可以引起依赖它的widget重新构建。
ChangeNotifier:观察者模式的代码模型。
setState
状态管理最基础的一个实现
class _SetStateDemoWidgetState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(setStateDemoTitle),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(“计数:$count”),
RaisedButton(
child: Text(“increment”),
onPressed: () => setState(() => count++),
)
],
),
),
);
}
}
复制代码
InheritedWidget
代码入口
用CountProvider继承InheritedWidget来保存数据。
通过context.getElementForInheritedWidgetOfExactType拿到CountProvider中的数据。 这个context一定要注意,只能用CountProvider子widget的BuildContext。CountProvider的查找是通过context往上的。
注意这个地方,我们只是单纯的拿数据,还没有用到InheritedWidget可以控制子widget重新构建。
///首先我们要有个地方存放我们的数据
class CountProvider extends InheritedWidget {
final int count;
const CountProvider({Key? key, required this.count, required Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(CountProvider old) {
return true;
}
}
class ProviderDemoWidget1 extends StatefulWidget {
…
}
class _ProviderDemoWidget1State extends State {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(providerDemoTitle1),
),
body: CountProvider(
count: _count,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Builder(
builder: (context2) {
/// 读取和显示计数
CountProvider provider = (context2
.getElementForInheritedWidgetOfExactType()
?.widget) as CountProvider;
return Text(“计数:${provider.count}”);
},
),
ElevatedButton(
child: const Text(“increment”),
onPressed: () => setState(() => _count++),
),
const Text(providerDemoIntroduction1),
],
),
),
),
);
}
}
复制代码
Provider(InheritedWidget + ChangeNotifier)
我们先看效果图,我们看到有三种场景。只有依赖的组件更新了UI。
代码入口
CountModel封装示例1中的count,继承ChangeNotifier,具备notify能力。
class CountModel extends ChangeNotifier {
int count;
CountModel(this.count);
void increment() {
count++;
notifyListeners();
}
}
复制代码
Provider继承InheritedWidget,封装两种访问:
context.dependOnInheritedWidgetOfExactType
context.getElementForInheritedWidgetOfExactType
区别在于,当InheritedWidget重新构建时,前者widget会重新构建,后者则不会。
class CountProvider extends InheritedWidget {
final CountModel model;
const CountProvider({Key? key, required this.model, required Widget child}) : super(key: key, child: child);
static CountModel of(BuildContext context, bool depend) {
var provider = depend
? context.dependOnInheritedWidgetOfExactType()
: context.getElementForInheritedWidgetOfExactType()?.widget;
return (provider as CountProvider).model;
}
@override
bool updateShouldNotify(CountProvider old) {
return true;
}
}
复制代码
ChangeNotifierProvider通过监听ChangeNotifier,将setState封装在其内部。总之,把状态管理(数据修改和Widget刷新)封装在自定义的对象内。外部控件不需要再关心状态管理的细节了。
class ChangeNotifierProvider extends StatefulWidget {
final Widget child;
final CountModel model;
ChangeNotifierProvider({Key? key, required this.child, required this.model}) : super(key: key);
@override
_ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}
class _ChangeNotifierProviderState extends State {
_ChangeNotifierProviderState();
_update() {
setState(() => {});
}
@override
void initState() {
super.initState();
widget.model.addListener(_update);
}
@override
void dispose() {
super.dispose();
widget.model.removeListener(_update);
}
@override
Widget build(BuildContext context) {
return CountProvider(
model: widget.model,
child: widget.child,
);
}
}
复制代码
有了 CountModel,Provider,ChangeNotifierProvider,简易版状态管理Provider库也就写好了,下面用起来:
class ProviderDemoWidget2 extends StatefulWidget {
…
}
class _ProviderDemoWidget2State extends State {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text(providerDemoTitle2)),
body: ChangeNotifierProvider(
model: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(“计数:%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder(builder%3A%20(context1)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text(%22%E8%AE%A1%E6%95%B0%3A#card=math&code=%7B_countModel.count%7D%22%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder%28builder%3A%20%28context1%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text%28%22%E8%AE%A1%E6%95%B0%3A&id=JJc2P){CountProvider.of(context1, true).count}(有依赖情况)”);
}),
Builder(builder: (context2) {
return Text(“计数:${CountProvider.of(context2, false).count}(无依赖情况)”);
}),
ElevatedButton(child: const Text(“increment”), onPressed: () => _countModel.increment()),
const Text(providerDemoIntroduction2),
],
),
),
),
);
}
}
复制代码
基于上个版本的通用泛型封装
上一个示例中CountModel不具有通用性,所以我们写一个泛型版本
代码入口
class Provider extends InheritedWidget {
final T model;
Provider({Key? key, required this.model, required Widget child}) : super(key: key, child: child);
static T of(BuildContext context, bool depend) {
var provider = depend
? context.dependOnInheritedWidgetOfExactType
: context.getElementForInheritedWidgetOfExactType
return (provider as Provider).model as T;
}
@override
bool updateShouldNotify(Provider old) {
return true;
}
}
class ChangeNotifierProvider extends StatefulWidget {
final Widget child;
final T model;
ChangeNotifierProvider({Key? key, required this.child, required this.model}) : super(key: key);
@override
_ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}
class _ChangeNotifierProviderState extends State {
_ChangeNotifierProviderState();
_update() {
setState(() => {});
}
@override
void initState() {
super.initState();
widget.model.addListener(_update);
}
@override
void dispose() {
super.dispose();
widget.model.removeListener(_update);
}
@override
Widget build(BuildContext context) {
return Provider(
model: widget.model,
child: widget.child,
);
}
}
复制代码
这样一个简单的ChangeNotifierProvider就实现了。
demo使用
class ProviderDemoWidget3 extends StatefulWidget {
…
}
class _ProviderDemoWidget3State extends State {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( title: const Text(providerDemoTitle3)),
body: ChangeNotifierProvider(
model: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Builder(builder: (context1) {
return Text(“计数:.count%7D%EF%BC%88%E6%9C%89%E4%BE%9D%E8%B5%96%E6%83%85%E5%86%B5)%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder(builder%3A%20(context2)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text(%22%E8%AE%A1%E6%95%B0%3A#card=math&code=%7BProvider.of%3CCountModel%3E%28context1%2C%20true%29.count%7D%EF%BC%88%E6%9C%89%E4%BE%9D%E8%B5%96%E6%83%85%E5%86%B5%29%22%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder%28builder%3A%20%28context2%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text%28%22%E8%AE%A1%E6%95%B0%3A&id=AlFDH){Provider.of(context2, false).count}(无依赖情况)”);
}),
ElevatedButton(child: const Text(“increment”), onPressed: () => _countModel.increment()),
const Text(providerDemoIntroduction3),
],
),
),
),
);
}
}
class ProviderDemoWidget3 extends StatefulWidget {
…
}
class _ProviderDemoWidget3State extends State {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
…仅修改访问部分,增加泛型支持
Builder(builder: (context1) {
return Text(“计数:.count%7D%EF%BC%88%E6%9C%89%E4%BE%9D%E8%B5%96%E6%83%85%E5%86%B5)%22)%3B%0A%20%20%20%20%7D)%2C%0A%20%20%20%20Builder(builder%3A%20(context2)%20%7B%0A%20%20%20%20%20%20%20%20return%20Text(%22%E8%AE%A1%E6%95%B0%3A#card=math&code=%7BProvider.of%3CCountModel%3E%28context1%2C%20true%29.count%7D%EF%BC%88%E6%9C%89%E4%BE%9D%E8%B5%96%E6%83%85%E5%86%B5%29%22%29%3B%0A%20%20%20%20%7D%29%2C%0A%20%20%20%20Builder%28builder%3A%20%28context2%29%20%7B%0A%20%20%20%20%20%20%20%20return%20Text%28%22%E8%AE%A1%E6%95%B0%3A&id=p53Lj){Provider.of(context2, false).count}(无依赖情况)”);
}),
…
}
}
复制代码
真正Provider的用法
举例Provider库中ChangeNotifierProvider使用。
其中使用到了Consumer,通过Provider.of(context)封装对model的访问
正如介绍所描述的非常简单。当然了Provider库,可维护性,可测试性,可扩展性远比我们所写的强大。
class ProviderDemoWidget4 extends StatefulWidget {
…
}
class _ProviderDemoWidget4State extends State {
final CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text(providerDemoTitle4)),
body: ChangeNotifierProvider.value(
value: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer(
builder: (contextC, model, child) {
return Text(“计数:%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder(builder%3A%20(context2)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text(%22%E8%AE%A1%E6%95%B0%3A#card=math&code=%7Bmodel.count%7D%EF%BC%88%E6%9C%89%E4%BE%9D%E8%B5%96%E6%83%85%E5%86%B5%29%22%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Builder%28builder%3A%20%28context2%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20Text%28%22%E8%AE%A1%E6%95%B0%3A&id=I4che){Provider.of(context2, listen: false).count}(无依赖情况)”);
}),
ElevatedButton(child: const Text(“increment”), onPressed: () => _countModel.increment()),
const Text(providerDemoIntroduction4),
],
),
),
),
);
}
}
复制代码
到此为止,我们也就了解了Provider的基本原理和基本使用。
可我们的征程才开始。知其然,更要知其所以然。接下来,我们从setState开始一步步分析Provider状态管理的过程。
作者:mwq30123
链接:https://juejin.cn/post/6844904115428917262
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。