flutter riverpod原理浅析

简介

Riverpod是一款flutter端用于管理状态器,类似于Provider库,都是同一个作者所写,在Provider的基础上提供了更强大的能力,作为一款现代的状态管理器,我认为应该具有的基本能力

  1. 状态的生命周期管理,提供状态的创建、缓存、销毁等能力
  2. 数据的共享能力
  3. 响应式,数据驱动UI变化

带着这三个能力去看Riverpod是怎么实现的

简单使用例子

scala 复制代码
void main() {
  runApp(ProviderScope(  //需要有一个ProviderScope,这个scope是状态的容器
    child: MyApp(),
  ));
}

//实现riverpod自带的ConsumerStatefulWidget
class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    ref.watch(fileNotifierProvider);
    return Container();
  }
}

ProviderScope是什么?

在Riverpod里面,ProviderScope是一个产生ProviderContainer的组件,ProviderContainer是用于管理provider和状态的容器,provider本身是不管理状态的。这种管理的方式是一种中心化的管理思想,因此可以在ProviderContainer里面去获取到所有provider和与之对应的状态。

scala 复制代码
class ProviderScope extends StatefulWidget {}

class ProviderScopeState extends State<ProviderScope> {
    Widget build(BuildContext context) {
  ....

  return UncontrolledProviderScope(
    container: container,
    child: widget.child,
  );
}
}

class UncontrolledProviderScope extends InheritedWidget {
    /// The [ProviderContainer] exposed to the widget tree.
    final ProviderContainer container;
}

class ProviderContainer implements Node {
    //ProviderContainer里面有一个map,保存provider和statereader状态读取器的映射
    final Map<ProviderBase<Object?>, _StateReader> _stateReaders;
}

ProviderScope build出UncontrolledProviderScope这样一个InheritedWidget,在flutter的子组件是有办法通过context.getElementForInheritedWidgetOfExactType去拿父组件的InheritedWidget,进一步拿到ProviderContainer

WidgetRef

使用Riverpod一般来说要使用提供的ConsumerStatefulWidgetConsumerWidget,这两个组件用于提供WidgetRef,这个类似于Provider里面context的功能,是widget组件和状态的桥梁,用于获取provider的数据和一些操作

csharp 复制代码
abstract class WidgetRef {
  
T watch<T>(ProviderListenable<T> provider);
  
bool exists(ProviderBase<Object?> provider);

 T read<T>(ProviderListenable<T> provider);
  
@useResult
  State refresh<State>(Refreshable<State> provider);
void invalidate(ProviderOrFamily provider);
}

WidgetRef是怎么来的?

scala 复制代码
abstract class ConsumerWidget extends ConsumerStatefulWidget {
  _ConsumerState createState() => _ConsumerState();
}

class _ConsumerState extends ConsumerState<ConsumerWidget> {
  @override
  Widget build(BuildContext context) {
    return widget.build(context, ref); //调用widget的build方法
  }
}

//-----------------------------------//

abstract class ConsumerStatefulWidget extends StatefulWidget {
const ConsumerStatefulWidget({super.key});
  @override
  // ignore: no_logic_in_create_state
  ConsumerState createState();

  @override
  ConsumerStatefulElement createElement() {
    return ConsumerStatefulElement(this);
  }
}

abstract class ConsumerState<T extends ConsumerStatefulWidget>
    extends State<T> {
    //这里说明了ref其实就是context,这两个是同一个东西
late final WidgetRef ref = context as WidgetRef;
}

// ConsumerStatefulElement继承了StatefulElement并实现了WidgetRef接口
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {}

read流程

csharp 复制代码
ref.read(fileNotifierProvider)

//ConsumerStatefulElement.read
T read<T>(ProviderListenable<T> provider) {
  _assertNotDisposed();
  return ProviderScope.containerOf(this, listen: false).read(provider);
}

//ProviderContainer.containerOf
static ProviderContainer containerOf(
  BuildContext context, {
  bool listen = true,
}) {
  UncontrolledProviderScope? scope;

  if (listen) {
    scope = context //
        .dependOnInheritedWidgetOfExactType<UncontrolledProviderScope>();
  } else {
    scope = context
        .getElementForInheritedWidgetOfExactType<UncontrolledProviderScope>()
        ?.widget as UncontrolledProviderScope?;
  }

  return scope.container;
}
kotlin 复制代码
//ProviderContainer.read
Result read<Result>(
  ProviderListenable<Result> provider,
) {
  return provider.read(this);
}

//ProviderBase.read
//ProviderBase是Provider的基础类
@override
StateT read(Node node) {
  //在ProviderContainer里面去注册当前的Provider和与之对应的StateReader,并创建一个ProviderElement实例
  final element = node.readProviderElement(this);
  
  //触发provider的build方法,并把build出来的state状态保存在element中
  element.flush();

  //销毁流程
  element.mayNeedDispose();
  //获取状态
  return element.requireState;
}

//ProviderContainer.readProviderElement
@override
  ProviderElementBase<State> readProviderElement<State>(
    ProviderBase<State> provider,
  ) {
    //去创建一个StateReader状态读取器,StateReader主要是去处理Provider在scope下可以overrider的
    //情况,一般使用比较少
    final reader = _putIfAbsent(provider);
    //在StateReader里面创建与之对应的ProviderElementBase
    return reader.getElement() as ProviderElementBase<State>;
  }

ProviderElementBase类是处理状态state和依赖的核心类,用于保存状态,以及依赖关系,状态变化导致的依赖通知等功能

dart 复制代码
//ProviderElementBase类
  abstract class ProviderElementBase<StateT> implements Ref<StateT>, Node {
  
@override
  ProviderContainer get container => _container;
  late final ProviderContainer _container;

  //我依赖谁
  var _dependencies = HashMap<ProviderElementBase<Object?>, Object>();
  HashMap<ProviderElementBase<Object?>, Object>? _previousDependencies;
  List<ProviderSubscription>? _subscriptions;
  //谁依赖我
  List<ProviderSubscription>? _dependents;

 
  /// Whether the element was disposed or not
@internal
  bool get mounted => _mounted;
  bool _mounted = false;


  /* STATE */
  Result<StateT>? _state;
  
@internal
  void setState(StateT newState) {
    
    final previousResult = getState();
    final result = _state = ResultData(newState);

    if (_didBuild) {
      _notifyListeners(result, previousResult);
    }
  }
  
@internal
  Result<StateT>? getState() => _state;
  }

状态的创建

scss 复制代码
//ProviderElementBase类
@internal
void flush() {
  _maybeRebuildDependencies();
  if (_mustRecomputeState) {
    _mustRecomputeState = false;
    _performBuild();
  }
}

void _performBuild() {
  final previousDependencies = _previousDependencies = _dependencies;
  _dependencies = HashMap();

  final previousStateResult = _state;
  //由ProviderElementBase子类去实现,一般是调用provider.build方法
  buildState();
  //这里创建出来的状态和之前的状态进行比较,比的是不是同一个对象,如果是同一个对象的话,那么不更新
  //这也是为什么riverpod的更新状态必须要调用state=xxx,这个方法。
  //如果之前的state是一个对象的话,一般来说用调用copyWith更改里面一个字段,生成新对象再赋值
  if (!identical(_state, previousStateResult)) {
    _notifyListeners(_state!, previousStateResult);
  }
}
scala 复制代码
class FileNotifier extends _$FileNotifier {
  @override
  FileModel build() {
    print("TAGTAG 重建了");
    return FileModel(fileName: "22");
  }

  void updateState(FileModel f) {
    state = f.copyWith(fileName:"yll");
  }
}

read流程总结

暂时无法在飞书文档外展示此内容

watch流程

watch流程主要是处理组件的依赖关系,当数据更新之后,通知组件需要重建

dart 复制代码
ref.watch(fileNotifierProvider);

//ConsumerStatefulElement.watch
Res watch<Res>(ProviderListenable<Res> target) {
  //判断是否已经依赖了provider,避免重复依赖
  return _dependencies.putIfAbsent(target, () {
    final oldDependency = _oldDependencies?.remove(target);

    if (oldDependency != null) {
      return oldDependency;
    }

    return _container.listen<Res>(
      target,
      (_, __) => markNeedsBuild(), //数据更新之后的回调,通知widget rebuild
    );
  }).read() as Res;
}

//ProviderBase.addListener
ProviderSubscription<StateT> addListener(
  Node node,
  void Function(StateT? previous, StateT next) listener, {
  required void Function(Object error, StackTrace stackTrace)? onError,
  required void Function()? onDependencyMayHaveChanged,
  required bool fireImmediately,
}) {
  //和read流程一样,构建Container和provider之间的关系
  final element = node.readProviderElement(this);

  element.flush(); //生成state

  //构建一个监听 _ProviderStateSubscription
  return _ProviderStateSubscription<StateT>(
    node,
    listenedElement: element,
    listener: (prev, next) => listener(prev as StateT?, next as StateT),
    onError: onError,
  );
}

//_ProviderStateSubscription构造函数
_ProviderStateSubscription(
  super.source, {
  required this.listenedElement,
  required this.listener,
  required this.onError,
}) {
  final dependents = listenedElement._dependents ??= [];
  dependents.add(this);
}

状态更新

scss 复制代码
void setState(StateT newState) {
  final previousResult = getState();
  final result = _state = ResultData(newState);

  if (_didBuild) {
    _notifyListeners(result, previousResult);
  }
}

暂时无法在飞书文档外展示此内容

到此为止,状态管理器的基本功能都已经实现了,值的创建,响应式数据驱动,状态的共享都有了

状态的销毁

AutoDisposeProvider

如果是AutoDisposeProvider,那么在此provider没有依赖的时候就会销毁状态

使用@riverpod默认生成的provider就是AutoDisposeNotifierProvider

第一种销毁时机,read时会去检测一次,如果没有依赖,那么read完就销毁状态

typescript 复制代码
//ProviderBase.read
@override
StateT read(Node node) {
  final element = node.readProviderElement(this);

  element.flush();

  // In case `read` was called on a provider that has no listener
  element.mayNeedDispose();

  return element.requireState;
}


/// 空实现
@protected
@visibleForOverriding
void mayNeedDispose() {}


//AutoDisposeProviderElementMixin
@override
void mayNeedDispose() {
  final links = _keepAliveLinks;

  // maintainState和links都是设置保持状态的api,maintainState已设置为deprecated
  if (!maintainState && !hasListeners && (links == null || links.isEmpty)) {
    _container.scheduler.scheduleProviderDispose(this);
  }
}

void scheduleProviderDispose(
  AutoDisposeProviderElementMixin<Object?> element,
) {
  _stateToDispose.add(element);
  _scheduleTask();
}

void _disposeProvider(ProviderBase<Object?> provider) {
  final reader = _getOrNull(provider);
  
  if (reader == null) return;

  reader._element?.dispose();

  if (reader.isDynamicallyCreated) {

    void removeStateReaderFrom(ProviderContainer container) {
      //实际的销毁函数
if (container._stateReaders[provider] == reader) {
        container._stateReaders.remove(provider);
      }
      container._children.forEach(removeStateReaderFrom);
    }

    removeStateReaderFrom(this);
  } else {
    reader._element = null;
  }
}

因此如果没有调用ref.watch,仅仅是使用ref.read,其实每一次读出来的状态不是同一个对象,每一次都会重新build一次状态。

第二种销毁时机,在element树销毁的时候。

scala 复制代码
//ConsumerState里面没有销毁逻辑
abstract class ConsumerState<T extends ConsumerStatefulWidget>
    extends State<T> {
  /// An object that allows widgets to interact with providers.
late final WidgetRef ref = context as WidgetRef;
}

///ConsumerStatefulElement
@override
void unmount() {
super.unmount();

  for (final dependency in _dependencies.values) {
    dependency.close();
  }
  for (var i = 0; i < _listeners.length; i++) {
    _listeners[i].close(); 
  }
  final manualListeners = _manualListeners?.toList();
  if (manualListeners != null) {
    for (final listener in manualListeners) {
      listener.close();
    }
    _manualListeners = null;
  }
}

///ProviderElementBase._onRemoveListener
void _onRemoveListener() {
  _onRemoveListeners?.forEach(runGuarded);
  if (!hasListeners) {
    _didCancelOnce = true;
    _onCancelListeners?.forEach(runGuarded);
  }
  mayNeedDispose();
}

非AutoDisposeProvider

  1. 如果不是AutoDisposeProvider,那么默认状态是永久的存留,因为其没有实现mayNeedDispose,使用keepAlive参数可以生成非AutoDisposeProvider

这种情况如果想刷新provider的状态该怎么办?WidgetRef有两个api用于重新计算状态

csharp 复制代码
 /// Forces a provider to re-evaluate its state immediately,
///and return the created value.
///立即计算状态,并返回
@useResult
State refresh<State>(Refreshable<State> provider);

/// Invalidates the state of the provider, causing it to refresh.
///
/// As opposed to [refresh], the refresh is not immediate and is instead
/// delayed to the next read or next frame.
///
/// Calling [invalidate] multiple times will refresh the provider only
/// once.
///
/// Calling [invalidate] will cause the provider to be disposed immediately.
///
/// If used on a provider which is not initialized, this method will have no effect.
void invalidate(ProviderOrFamily provider);

Consumer与Selector

这两个类主要是为了减小状态的刷新范围

比如在一棵widget树下面,如果有一个小组件需要刷新,我们当然是希望小组件单个刷新,而不会引起整个页面的刷新,一般来说有两个方法

  1. 把组件抽离成一个单独的widget
  2. 用Consumer把小组件包起来,形成一个单独的刷新区域

Selector的作用是在一个大的State里面包含好几个字段,只有里面一个字段发生变化的时候state都会重置,引起状态更新,但是如果只需要里面某个特定字段发生变化的时候才引起更新,这时候就需要Selector

less 复制代码
@freezed
class FileModel with _$FileModel {

  const factory FileModel({

    ///文件名
@Default("") String fileName,

    @Default(0) int num,
    ///

}) = _FileModel;
}


//使用
body: Container(
  child: ProviderScope(
    child: Consumer(
      builder: (context, ref, _) {
        var fileName = ref.
                watch(fileNotifierProvider.select((FileModel model) => model.fileName));
        return Text(fileName);
      },
    ),
  ),
),

selector会比较前后两次的值,如果值不相等才会去更新

dart 复制代码
//_ProviderSelector
void _selectOnChange({
  required Input newState,
  required Result<Output> lastSelectedValue,
  required void Function(Object error, StackTrace stackTrace) onError,
  required void Function(Output? prev, Output next) listener,
  required void Function(Result<Output> newState) onChange,
}) {
  final newSelectedValue = _select(Result.data(newState));
  if (!lastSelectedValue.hasState ||
      !newSelectedValue.hasState ||
      lastSelectedValue.requireState != newSelectedValue.requireState) {

    onChange(newSelectedValue);
    
    newSelectedValue.map(
      data: (data) {
        listener(
          // TODO test from error
          lastSelectedValue.stateOrNull,
          data.state,
        );
      },
      error: (error) => onError(error.error, error.stackTrace),
    );
  }
}

Family Provider

可以简单理解为带参数的provider,本质上是为不同参数生成不同的provider,然后再生成不同的目标对象

使用注解生成family provider

typescript 复制代码
@riverpod
List<FileModel> filteredFileList(FilteredFileListRef ref 这个类是生成的, String keyword) {
  final files = ref.watch(fileNotifierProvider);
  if (keyword.isEmpty) return files;
  return files.where((f) => f.fileName.contains(keyword)).toList();
}

使用

csharp 复制代码
ref.read(filteredFileListProvider(_keyword));

filteredFileListProvider是一个family

scala 复制代码
class FilteredFileListFamily extends Family<List<FileModel>>

这个类带一个call函数,call函数是能使得对象带有函数功能的方法,其实和对象调用普通方法差不多

javascript 复制代码
FilteredFileListProvider call(
  String keyword,
) {
  return FilteredFileListProvider(
    keyword,
  );
}

call方法返回真正的provider

python 复制代码
FilteredFileListProvider(
  String keyword,
) : this._internal(
        (ref) => filteredFileList(
          ref as FilteredFileListRef,
          keyword,
        ),
        from: filteredFileListProvider,
        name: r'filteredFileListProvider',
        debugGetCreateSourceHash:
            const bool.fromEnvironment('dart.vm.product')
                ? null
                : _$filteredFileListHash,
        dependencies: FilteredFileListFamily. _dependencies,
        allTransitiveDependencies:
            FilteredFileListFamily. _allTransitiveDependencies,
        keyword: keyword,
      );

所以说原理也是比较简单的,之前使用的provider是生成全局的一个provider,不会变;family是每次生成新的provider

使用注解生成代码

现在的riverpod推荐使用注解生成代码的形式,免去了一些provider代码的流程。使用Riverpod一般来说需要自己写一个Notiier类,用于管理state创建以及state更新逻辑;还需要写一个state类,这个state类通常用freezed注解去生成,主要是生成了json格式化的代码和copyWith代码,用于快速新生成一个State对象。

推荐两个插件用于生成Notifier和State模板代码的插件

生成Notifier,只需要打出river

生成的代码

生成freezed类

生成part语句,对应freezedPart功能

dart 复制代码
part 'test3.freezed.dart';
相关推荐
恋猫de小郭3 小时前
深入理解 Flutter 的 PlatformView 如何在鸿蒙平台实现混合开发
android·前端·flutter
浅蓝色3 小时前
flutter平台判断后续
flutter·harmonyos
猪哥帅过吴彦祖4 小时前
Flutter 系列教程:常用基础组件 (下) - `TextField` 和 `Form`
前端·flutter·ios
我想吃辣条5 小时前
flutter google play 应用不支持 16 KB
android·flutter
LinXunFeng5 小时前
Flutter - Melos Pub workspaces 实践
前端·flutter·架构
勤劳打代码18 小时前
妙笔生花 —— Flutter 实现飞入动画
前端·flutter·设计模式
我想吃辣条21 小时前
flutter mapbox_maps_flutter 应用不支持 16 KB
flutter
Aftery的博客1 天前
flutter项目打包macOS桌面程序dmg
flutter·macos