转载请注明出处:https://blog.csdn.net/llew2011/article/details/105450640
Flutter开发过程中一个常见的问题就是状态管理,所谓状态管理就是管理Flutter的Widget状态,对于Flutter的状态管理,社区上已有多种成熟的方案:Provider、Redux、MobX、BLoC等。在这些方案里Google建议我们使用Provider,接下来我们就学习下Provider,看它是如何做到的状态管理,在了解其原理之前,我们先看下它的使用。
首先创建Flutter项目providerDemo,创建完毕后在根目录下找到pubspec.yaml
文件,在dependencies下追加provider依赖,如下所示:
name: flutter_provider
description: A new Flutter application.
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
provider: 3.2.0 // 添加privider的依赖,注意格式
dev_dependencies:
flutter_test:
sdk: flutter
pubspec.yaml
是flutter的配置文件,它与Android项目中的build.gradle
功能类似,添加完依赖后状态栏会弹出4个能选项:Package get , Package upgrade, Flutter upgrade以及Flutter doctor,直接点击Packages get就会自动加载依赖,截图如下:
假设我们有三个页面,他们分别是A页面B页面和C页面,其中A页面是主页面(Flutter项目启动后最先展示的页面),它展示了一个用户的姓名和年龄,然后从A页面跳转到B页面,在B页面可以修改用户的姓名,之后又从B页面跳转到C页面并在C页面又修改了用户的年龄,修改完成之后直接返回到A页面,返回到A页面后应该展示的是修改后的值。这个小功能在Android中能很容易实现,接下来我们看看在Flutter中该如何做。
创建UserModel
A页面展示的是用户姓名和年龄,我们可以定义一个UserModel
(有用户名和年龄属性),然后在B页面和C页面分别修改UserModel
的姓名和年龄(提供修改姓名和年龄的方法),修改后可以触发页面更新从而使A页面展示更新后的值。UserModel
定义如下:
class UserModel with ChangeNotifier {
String _name;
int _age;
UserModel(this._name, this._age);
void updateName(String name) {
this._name = name;
notifyListeners();
}
void updateAge(int age) {
this._age = age;
notifyListeners();
}
get name => _name;
get age => _age;
}
UserModel
定义了_name 和 _age私有属性并对外提供了updateName()和updateAge()方法,在这些updateXXX()方法内调用了notifyListeners()方法,notifyListeners()方法是ChangeNotifier
类中提供的,ChangeNotifier
是Flutter提供的具有观察者功能的类,UserModel
使用with关键字表示它具有ChangeNotifier
的所有功能,我们稍微看下ChangeNotifier
的源码,如下所示:
class ChangeNotifier implements Listenable {
// 监听器容器
ObserverList _listeners = ObserverList();
@override
void addListener(VoidCallback listener) {
// 添加监听器
}
@override
void removeListener(VoidCallback listener) {
// 移除监听器
}
void notifyListeners() {
// 通知所有监听器,触发回调
}
}
ChangeNotifier
实现了Listenable
接口,它对外提供了注入监听器,移除监听器以及触发监听器回调等功能,因为UserModel
使用了with关键字聚合了ChangeNotifier
的这些功能,所以UserModel
本质上也是一个具有观察者功能的类。
注入UserModel
创建完UserModel
后,使用Provider库提供的ChangeNotifierProvider
类给当前应用注入一个UserModel
实例,注入之后就可以在其它页面使用该实例。在mian()方法做如下修改:
void main() {
// runApp(MyApp()); // 注释掉runApp()方法,修改如下
var newWidget = ChangeNotifierProvider.value(
value: UserModel("张三", 22),
child: MyApp(),
);
runApp(newWidget);
}
main()方法是flutter的入口,该方法内仅调用了runApp()方法,runApp()方法接收一个Widget
类型的参数,我们使用ChangeNotifierProvider
的命名构造方法value()
创建一个newWidget实例并把newWidget实例传给了runApp()方法,在构造newWidget实例时给newWidget传递了一个UserModel
实例和MyApp
实例。修改完main()方法后我们开始创建A、B、C三个页面。
使用UserModel
首先创建主页面A,在A页面中展示UserModel
的name和age,如下所示:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'A页面'),
navigatorObservers: [
GlobalNavigatorObserver(),
],
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends BaseState {
@override
Widget buildContent(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("用户名:${Provider.of(context).name}"),// 使用UserModel的name值
Text("年 龄: ${Provider.of(context).age}"),// 使用UserModel的age值
RaisedButton(
child: Text("打开设置用户名页面"),
// 跳转到B页面
onPressed: () => NavigatorHelper.push(SettingNameWidget()),
),
],
),
),
);
}
}
A页面用两个文本框和一个按钮(文本框用来展示用户名和年龄,按钮用来做页面跳转),展示姓名和年龄时是通过Provider提供的of()静态方法获取到了刚刚注入的UserModel
实例然后分别获取姓名和年龄值。当点击了按钮后页面会跳转打B页面(NavigatorHelper
是封装的一个不依赖BuildContext就可以进行页面跳转的辅助类)。B页面布局如下:
class SettingNameWidget extends StatefulWidget {
@override
State createState() {
return _SettingNameState();
}
}
class _SettingNameState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("设置用户名页面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("设置用户名"),
TextField(
showCursor: true,
onChanged: (value) {
// 更新name
Provider.of(context).updateName(value);
},
),
RaisedButton(
child: Text("打开设置年龄页面"),
onPressed: () {
// 跳转到C页面
NavigatorHelper.push(SettingAgeWidget());
},
),
],
),
),
);
}
}
B页面包含了一个输入框和按钮,在输入数据后会调用Provider的of()方法获取UserModel
实例并设置UserModel
的name值,点击按钮后跳转打C页面,C页面布局如下:
class SettingAgeWidget extends StatefulWidget {
@override
State createState() {
return _SettingAgeState();
}
}
class _SettingAgeState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("设置年龄页面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("设置年龄"),
TextField(
showCursor: true,
keyboardType: TextInputType.number,
onChanged: (value) {
// 更新年龄
Provider.of(context).updateAge(int.parse(value));
},
),
RaisedButton(
child: Text("返回首页"),
onPressed: () => NavigatorHelper.popUntil(ModalRoute.withName("/")),
)
],
),
),
);
}
}
C页面添加了一个输入框和一个按钮,当输入框中的数据改变后会修改UserModel
的age值,最后点击按钮返回到A页面,此时A页面就显示的是修改后的名字和年龄,演示如下:
以上就是Provider的简单使用,通过这个示例向小伙伴们展示了如何使用Provider,接下来我给小伙伴们介绍一下Provider的工作原理,看看Provider是如何做到跨页面共享状态的。
根据刚才的示例我们知道main.dart的main()方法调用了runApp()方法,而runApp()方法接收一个Widget
类型的参数,通过使用ChangeNotifierProvider
的命名构造方法value()生成了一个ChangeNotifierProvider
实例newWidget然后把newWidget传递给了runApp()方法,那也就是说ChangeNotifierProvider
一定是Widget
的子类。我们就看下ChangeNotifierProvider
的源码,如下所示:
class ChangeNotifierProvider
extends ListenableProvider implements SingleChildCloneableWidget {
// 省略相关代码
ChangeNotifierProvider.value({
Key key,
@required T value,
Widget child,
}) : super.value(key: key, value: value, child: child);
}
ChangeNotifierProvider
继承了ListenableProvider
,它除了定义了两个构造方法外,什么都没有做,继续看下它的父类ListenableProvider
,源码如下:
class ListenableProvider extends ValueDelegateWidget
implements SingleChildCloneableWidget {
// 包含一个Widget实例,该实例是刚刚传递进来的MyApp
final Widget child;
// 省略相关代码
ListenableProvider.value({
Key key,
@required T value,
Widget child,
}) : this._(
key: key,
delegate: _ValueListenableDelegate(value), // 创建一个Delegate
child: child,
);
// 重写了build()方法,返回InheritedProvider实例
@override
Widget build(BuildContext context) {
final delegate = this.delegate as _ListenableDelegateMixin;
return InheritedProvider(
value: delegate.value,
updateShouldNotify: delegate.updateShouldNotify,
child: child, // 把child传递给InheritedProvider
);
}
}
ListenableProvider
继承了ValueDelegateWidget
也提供了一个value()命名构造方法并重写了build()方法,它又定义了一个Widget
类型的child属性,因为我们在创建ChangeNotifierProvider
的时候传递的child是MyApp实例,所以当前的child就是MyApp实例。ListenableProvider
的build()方法返了一个InheritedProvider
实例,在创建InheritedProvider
实例的时候把child传递给了InheritedProvider
。我们继续看InheritedProvider
的源码,如下所示:
class InheritedProvider extends InheritedWidget {
const InheritedProvider({
Key key,
@required T value,
UpdateShouldNotify updateShouldNotify,
Widget child,
}) : _value = value,
_updateShouldNotify = updateShouldNotify,
super(key: key, child: child);// 把child传递给了InheritedWidget,也就是把MyApp传递给了InheritedWidget
final T _value; // _value就是UserModel实例
final UpdateShouldNotify _updateShouldNotify;
@override
bool updateShouldNotify(InheritedProvider oldWidget) {
if (_updateShouldNotify != null) {
return _updateShouldNotify(oldWidget._value, _value);
}
return oldWidget._value != _value;
}
}
通过InheritedProvider
的源码我们发现它继承自InheritedWidget
,InheritedWidget
是Flutter中比较重要的一个功能型Widget
,它提供了一种数据可以在Widget
树中从上到下传递、共享的方式,比如我们在应用的根Widget
中通过InheritedWidget
共享了一个数据,那么我们便可以在任意的子Widget
中来获取该共享数据,Provider的原理恰好是通过该特性实现了状态的跨组件共享(#^.^#)。这也是Provider要求我们在main()方法做修改的目的,就是把根Widget(MyApp)替换成InheritedWidget
,然后把MyApp作为InheritedWidget
的子Widget
,操作过程如下所示:
到这里我们已经知道了Provider是通过InheritedWidget
实现了状态跨Widget
共享,那么它是如何触发页面更新的呢?继续看触发页面更新的时机,当调用了UserModel的setXXX()方法后会触发notifyListeners()方法,notifyListeners()方法是在ChangeNotifier
中定义的,源码如下:
void notifyListeners() {
if (_listeners != null) {
final List localListeners = List.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
// 循环遍历监听器,并执行监听器的回调函数listener
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
}
}
}
}
notifyListeners()方法会循环遍历注入的监听器并执行其回调,那么这些监听器是什么时候注入的呢?还记得ListenableProvider
的value()命名构造方法吗?它内部在调用私有构造方法的时候传递了一个_ValueListenableDelete
实例,代码如下:
ListenableProvider.value({
Key key,
@required T value,
Widget child,
}) : this._(
key: key,
delegate: _ValueListenableDelegate(value), // 传递了一个_ValueListenableDelegate
child: child,// MyApp实例
);
// _ValueListenableDelegate源码如下:
class _ValueListenableDelegate
extends SingleValueDelegate with _ListenableDelegateMixin {
_ValueListenableDelegate(T value, [this.disposer]) : super(value);
@override
void didUpdateDelegate(_ValueListenableDelegate oldDelegate) {
super.didUpdateDelegate(oldDelegate);
if (oldDelegate.value != value) {
_removeListener?.call();
oldDelegate.disposer?.call(context, oldDelegate.value);
// 调用startListening()方法
if (value != null) startListening(value, rebuild: true);
}
}
@override
void startListening(T listenable, {bool rebuild = false}) {
// 调用父类的startListening()方法
super.startListening(listenable, rebuild: rebuild);
}
}
// _ValueListenableDelegate的父类startListening方法如下:
void startListening(T listenable, {bool rebuild = false}) {
var buildCount = 0;
final setState = this.setState;
// listener是一个函数,当执行listener的时候会执行setState()方法
final listener = () => setState(() => buildCount++);
var capturedBuildCount = buildCount;
if (rebuild) capturedBuildCount--;
updateShouldNotify = (_, __) {
final res = buildCount != capturedBuildCount;
capturedBuildCount = buildCount;
return res;
};
// 添加监听器,此时参数listenable就是UserModel
// 调用UserModel的addListener()方法把listener函数注入
listenable.addListener(listener);
_removeListener = () {
listenable.removeListener(listener);
_removeListener = null;
updateShouldNotify = null;
};
}
通过对_ValueListenableDelete
的执行流程分析,我们知道在_ValueListenableDelegate
的didUpdateDelegate()方法内部startListening()方法,而startListening()方法最终会把setState()函数封装成参数添加进UserModel
的监听器容器中,当调用UserModel
的notifyListeners()方法时会循环遍历监听器容器并间接执行到setState()方法,而setState()方法是Flutter Framework层提供的刷新页面的函数,所以只要UserModel
的状态发生改变都会触发setState()函数从而刷新页面。
以上就是Provider的状态管理,它的原理就是合理利用了InheritedWidget
特性。由于篇幅原因,Provider的of()静态函数是如何获取到注入的UserModel
实例的,这个很简单就不再进行源码分析了。感兴趣的小伙伴可以自行分析(#^.^#)
或许有的小伙伴有疑问,应用开发中我们不仅仅是用户信息需要全局共享,假如其它信息也需要状态共享,那该怎么办呢?Provider库的开发者早已考虑到了多状态共享的情况,遇见多状态共享只需要简单的使用MultiProvider就行了,具体用法可参考Provider的WiKi。
另外画了一下Provider的流程图: