Flutter状态管理:从概念到Provider源码解析
1. 概念
🤔 忘记在哪看到过这样一句话:
状态(State)管理 ——响应式编程框架绕不过去的一道坎。
Flutter Widget 的核心设计理念之一也是 响应式 ,自然也需要面对这个问题。😆 在学习具体的状态管理框架前,先过下概念相关的东西~
1.1. 什么是状态?
答:应用中随时可能变化且影响UI的信息 ,如:用户交互 (文本输入)、从网络获取的数据 等。
1.2. Flutter中的状态分类
- 局部状态 (Local State) :仅在单个Widget内部使用和管理 ,如:复选框是否处于选中状态,并不需要共享给应用的其它部分。
- 全局状态 (Global State) :跨多个Widget共享的状态 ,如:用户的登录信息,需在多个页面中访问和显示。
😄 Flutter 中常说的状态管理,管的是 全局/共享状态 。
1.3. Flutter预置哪些状态管理方式?
1.3.1. setState()
最基础的状态管理方式 ,调用此方法告知Flutter框架,需要重新运行构建方法来更新UI。
局限 :主要适用于管理单个Widget或Widget树中 较小范围的局部状态 ,当应用规模扩大,需要 跨多个Widget共享和更新状态 时,会变得 非常复杂和低效 。
1.3.2. InheritedWidget
Flutter提供的功能性Widget,允许共享数据在Widget树中从上往下传递 ,而不需要通过 在每个Widget的构造方法中传递 。
局限 :需手动编写大量样板代码 (自定义InheritedWidget),子Widget中需显式声明依赖于哪个InheritedWidget ,灵活性有限 (如异步更新、更细粒度的条件更新等支持困难)
😐 综上,当应用中只有 少量简单状态需要管理 ,直接使用 setState() 和 InheritedWidget 就可以了。但对于 复杂的大型项目 (状态管理逻辑较为复杂的应用),则需要选择一个 更加高效、易维护的状态管理框架 。
1.4. 选状态管理框架要考虑的点有哪些?
- API简洁易用 :开发者可以快速上手,轻松实现状态管理,不需要编写大量的模板代码。
- 性能 :只在必须更新时更新状态,且尽可能减少重建Widget的次数,以保持应用的流畅性能。
- 状态一致性 :多个Widget需响应一个状态变化,得确保所有相关组件都能及时并正确响应状态变化。
- 可预测 :同样的状态始终导致相同的输出,数据流向清晰且易于追踪。
- 模块化和可重用 :支持状态的模块化,允许状态被封装并在应用的不同部分重用。
- 可扩展性、调试/测试便利性、异步支持、状态同步、灵活性、状态恢复和持久化、文档和社区支持等...
😄 哈哈,有点书面化了,个人感觉在做库的快速选型时可以参考这个三要素:Star数 、issues数 、最近Commit时间 。
2. Provider
官方推荐 ,基于InheritedWidget实现 ,允许开发者在应用的不同层级中 传递和监听状态变化 。
2.1. 基本用法
执行 flutter pub add provider 添加依赖,然后开耍~
2.1.1. 创建数据模型类 (继承ChangeNotifier)
在其中定义操作数据的方法,并调用 notifyListeners() 通知监听者数据变化:
import 'package:flutter/material.dart';
class LoginStatusModel extends ChangeNotifier {
bool _isLogin = false;
bool get isLogin => _isLogin;
void updateLoginStatus(bool isLogin) {
_isLogin = isLogin;
notifyListeners(); // 通知监听者数据变化
}
}
2.1.2. 提供状态数据
通常在 main.dart 的 main() 中的 MaterialApp 的上方,使用 Provider或其子类 ,包裹 App实例,并将 状态模型实例作为值传递 :
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => LoginStatusModel(),
child: const MyApp(),
),
);
}
2.1.3. 使用状态数据
在需要访问或监听数据变化的Widet中,使用 Provider.of() 、Consumer 获取:
class TextWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 Consumer 监听 CounterModel
return Consumer<LoginStatusModel>(
builder: (context, loginStatus, child) {
return Text('${loginStatus.isLogin}');
},
);
}
}
// 也可以使用 Provider.of() 来获取:
Text('${Provider.of<LoginStatusModel>(context, listen: false).isLogin}')
2.1.4. 多个需要共享的数据模型
可以使用 MultiProvider 来同时提供多个 Providers,代码示例如下:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginStatusModel()),
ChangeNotifierProvider(create: (context) => CounteModel()),
// 其他 Providers...
],
child: const MyApp(),
),
);
}
2.1.5. 处理异步数据
如果需要处理异步数据,可以使用 FutureProvider 或 StreamProvider ,代码示例如下:
FutureProvider<String>(
create: (context) => fetchData(),
initialData: '加载中...',
child: Consumer<String>(
builder: (context, data, child) {
return Text(data);
},
),
)
2.2. 一点细节
2.2.1. Provider.of() 的问题
Provider.of() 有一个参数 listen ,默认为true,即监听状态变化,调用了此方法的Widget将会被重建 。如果只是想在 Widget#build() 中访问状态,建议设置 listen: false 以减少不必要的重建。
2.2.2. Consumer 调优小技巧
Consumer有个可选的 Widget? child 参数,可以将 不需要在每次状态更新时重建的组件 放在这个参数中,这样可以 缩小控件的刷新范围 ,提高性能。
简单代码示例:
return Scaffold(
body: Consumer(
builder: (BuildContext context,CounterModel counterModel,Widget? child){
return Column(
children: [
Text("${counterModel.count}"),
ElevatedButton(
onPressed: ()=> counterModel.increment(),
child: const Text("点击加1"),
),
child! // 引用child组件
],
);
},
child: Column(
children: [
Text("不依赖状态变化的组件"),
],
),
),
);
2.2.3. 更细粒度的刷新-Selector
Consumer 用于监听Provider中 所有数据变化 ,Selector 则用于是监听 一个或多个值的变化 。简单代码示例:
class UserModel with ChangeNotifier {
String _name;
int _age;
UserModel({String name = 'CoderPig', int age = 30})
: _name = name,
_age = age;
String get name => _name;
int get age => _age;
set name(String newName) {
_name = newName;
notifyListeners(); // 通知监听者数据已更改
}
set age(int newAge) {
_age = newAge;
notifyListeners(); // 通知监听者数据已更改
}
}
// 调用处:当name发生变化时重建,age发生变化不重建
Selector<UserModel, String>(
selector: (_, model) => model.name,
builder: (_, name, __) {
return Text(name);
},
)
Tips :Selector 也有 Widget? child 参数,可用于设置不需要更新的Widget,提高性能
2.2.4. 快速调用扩展
库中有几个 BuildContext 的扩展,方便快速调用。
- ReadContext → BuildContext.read() :对应 Provider.of(),用于获取数据,不会触发刷新。
- WatchContext → BuildContext.watch() :对应Consumer(),只是不支持传child参数。
- SelectContext → BuildContext.select() :对应 Selector(),只是不支持传child参数。
2.3. 其它API
- FutureProvider :适用于异步数据获取,可以轻松地在数据加载时显示加载指示器。
- StreamProvider :适用于流式数据,如数据库更新、WebSocket 消息等。
- ProxyProvider :可以组合多个 Providers,并基于它们的输出创建新的数据。
- ListenableProvider :适用于任何实现了 Listenable 接口的类。
- ValueListenableProvider :适用于 ValueListenable 类型的数据。
2.4. 源码解读
😄 Provider的用法还是非常简单的,接着来扒下源码,了解库背后的设计原理,使用起来更加有的放矢。先复习下 InheritedWidget 的原理图吧,毕竟 Provider 是基于它进行的封装:
2.4.1. InheritedProvider
OK,开扒,先是 ChangeNotifier ,它是Flutter Framework提供的基础类,用于:在值改变时通知监听器 :
mixin class ChangeNotifier implements Listenable {
// 核心属性
int _count = 0; // 监听器计数
List<VoidCallback?> _listeners = _emptyListeners; // 监听器列表
// 核心方法
void addListener(VoidCallback listener) { /* 添加一个监听器 */ }
void removeListener(VoidCallback listener) { /* 移除监听器 */ }
void notifyListeners() { /* 通知所有注册的监听器,即遍历调用监听器注册的回调函数 */ }
void dispose() { /* 当对象不再需要调用时调用,它会移除所有监听器并释放资源 */ }
}
基于 观察者模式 实现,数据模型变化的方法中调用 notifyListeners() 通知所有监听器。那是啥时候添加和移除监听器的呢?看了下 ChangeNotifierProvider 没有找到相关调用,它继承了 ListenableProvider ,在此找到了方法调用:
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
ListenableProvider({
Key? key,
required Create<T> create,
Dispose<T>? dispose,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
}) : super(
key: key,
startListening: _startListening,
create: create,
dispose: dispose,
lazy: lazy,
builder: builder,
child: child,
);
static VoidCallback _startListening(
InheritedContext<Listenable?> e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
然后 _startListening() 作为参数 startListening 传递给父类 InheritedProvider 的构造方法,点开父类发现它竟是 所有 Provider的父类 ,源码注释中这样描述它:InheritedWidget的泛型实现 。简化版源码如下:
// 继承SingleChildStatelessWidget
class InheritedProvider<T> extends SingleChildStatelessWidget {
// 创建新的数据对象,并通过InheritedProvider使其在应用的widget树中可用
InheritedProvider({
Key? key,
Create<T>? create, // 当Provider首次插入Widget树时调用,用于创建数据。
T Function(BuildContext context, T? value)? update, // 当依赖的数据发生变化时调用,用于更新数据s
UpdateShouldNotify<T>? updateShouldNotify, // 数据更新后是否通知依赖InheritedWidget的Widgets
void Function(T value)? debugCheckInvalidValueType,
StartListening<T>? startListening, // 数据对象被创建后立即监听它的变化,可在此设置监听器
Dispose<T>? dispose, // 当Provider从Widget树中移除时调用
this.builder, // 接受一个BuildContext和Widget的子节点,可以用它来构建一个依赖于BuildContext的Widget
bool? lazy, // 控制Provider的懒加载行为,默认为true,延迟创建数据对象,直到它被首次请求
Widget? child,
}) : _lazy = lazy,
_delegate = _CreateInheritedProvider(
create: create,
update: update,
updateShouldNotify: updateShouldNotify,
debugCheckInvalidValueType: debugCheckInvalidValueType,
startListening: startListening,
dispose: dispose,
),
super(key: key, child: child);
// 已经有数据对象,且想在应用中共享时,可以使用这个构造方法
InheritedProvider.value({
Key? key,
required T value, // 需要共享的已经存在的数据对象
UpdateShouldNotify<T>? updateShouldNotify,
StartListening<T>? startListening,
bool? lazy,
this.builder,
Widget? child,
}) : _lazy = lazy,
_delegate = _ValueInheritedProvider(
value: value,
updateShouldNotify: updateShouldNotify,
startListening: startListening,
),
super(key: key, child: child);
// 传入delegate实例
InheritedProvider._constructor({
Key? key,
required _Delegate<T> delegate,
bool? lazy,
this.builder,
Widget? child,
}) : _lazy = lazy,
_delegate = delegate,
super(key: key, child: child);
final _Delegate<T> _delegate;
final bool? _lazy;
@override
_InheritedProviderElement<T> createElement() {
return _InheritedProviderElement<T>(this);
}
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return _InheritedProviderScope<T?>(
owner: this,
// ignore: no_runtimetype_tostring
debugType: kDebugMode ? '$runtimeType' : '',
child: builder != null
? Builder(
builder: (context) => builder!(context, child),
)
: child!,
);
}
}
🤔 定义了三种构造 InheritedProvider实例 的方法,差异点在于 _delegate属性 的赋值,依次为: _CreateInheritedProvider (数据对象随Provider创建而创建)、 _ValueInheritedProvider (对已有的数据对象进行共享)、直接传入delegate实例。
2.4.2. _Delegate & _DelegateState
点开这两个 私有Provider的父类 康康:
@immutable
abstract class _Delegate<T> {
// 创建一个_DelegateState类型的实例
_DelegateState<T, _Delegate<T>> createState();
}
abstract class _DelegateState<T, D extends _Delegate<T>> {
// 指向_InheritedProviderScopeElement的引用
_InheritedProviderScopeElement<T?>? element;
// 返回当前的数据对象
T get value;
// 通过Element访问当前代理并强转,提供一种访问和操作代理数据的方法
D get delegate => element!.widget.owner._delegate as D;
// 是否有可用的数据值
bool get hasValue;
// 代理即将更新时调用,默认返回false
bool willUpdateDelegate(D newDelegate) => false;
// 当状态对象被销毁时调用
void dispose() {}
// 根据提供的数据和状态构建UI或执行逻辑,isBuildFromExternalSources参数用于指示构建是否由外部源 (如数据变化) 触发
void build({required bool isBuildFromExternalSources}) {}
}
😳 哈?这 _Delegate 和 _DelegateState 跟 Widget 和 State 的关系有点像啊!这里是通过 代理 的方式来操作状态对象。接着依次看下 _DelegateState 的两个子类,先是 _CreateInheritedProviderState :
class _CreateInheritedProviderState<T>
extends _DelegateState<T, _CreateInheritedProvider<T>> {
VoidCallback? _removeListener; // 在不需要时移除监听器的回调
bool _didInitValue = false; // 是否初始化值的标记,即是否有可用的数据值
T? _value; // 存储状态数据
_CreateInheritedProvider<T>? _previousWidget; // 指向前一个Widget的引用,用于更新时比较
FlutterErrorDetails? _initError; // 存储初始化时发生的错误详情
@override
T get value {
// 检查是否在创建值时过程中抛出了异常,如果是抛出StateError
// 通过一系列化的断言和状态标志来确保在创建或更新值时,正确地锁定和解锁状态,防止在不合适的时机读取或修改值。
// 如果_didInitValue为false,说明值尚未初始化,尝试调用delegate.create() 或delegate.update() 来初始化或修改值。
if (!_didInitValue) {
_didInitValue = true;
if (delegate.create != null) {
_value = delegate.create!(element!);
}
if (delegate.update != null) {
_value = delegate.update!(element!, _value);
}
// 暂时禁用对依赖项的通知,即执行下述操作期间,即使 InheritedWidget 状态发生变化
// 也不不会立即通知依赖于它的Widget,防止更新过程中可能发生的不要的重建或更新。
element!._isNotifyDependentsEnabled = false;
// 尝试设置一个监听,以便在_value发生变化时做出响应
_removeListener ??= delegate.startListening?.call(element!, _value as T);
// 恢复对依赖项的通知
element!._isNotifyDependentsEnabled = true;
// 返回当前的状态数据
return _value as T;
}
}
@override
void