跟?杰哥一起学Flutter (十四、玩转状态管理之——Provider详解)

111 阅读6分钟

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. 处理异步数据

如果需要处理异步数据,可以使用 FutureProviderStreamProvider ,代码示例如下:

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 的扩展,方便快速调用。

  • ReadContextBuildContext.read() :对应 Provider.of(),用于获取数据,不会触发刷新。
  • WatchContextBuildContext.watch() :对应Consumer(),只是不支持传child参数。
  • SelectContextBuildContext.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_DelegateStateWidget 和 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