一些状态管理框架 provider get 实践中的注意问题

上一期文章 Flutter 主流状态管理框架 provider get 分析与思考 中,我和大家一起分析了 Flutter 中状态管理框架出现的原因,以及他们的设计思路。从整体上来看,状态管理可以分为两大类,一种是依赖于「Flutter 树机制」的框架,例如 provider、bloc 等;另一类是 get 这种「依赖注入」的框架。今天就来和大家分享一下,我在日常开发中遇到过的问题,以及我认为他们不太合理的一些设计。希望能帮助大家在日常开发中减少出错的可能。


一、ProviderNotFoundException!!!

依赖树机制的这类状态框架的问题,千言万语汇做一个异常

ProviderNotFoundException !!!!

是的,至今遇到的绝大部分的问题,都来自于这个异常。因为树机制依赖于我们使用的 context,一旦 context 不对,我们就能碰到这个异常,具体有以下场景:

1、context 层级高于 Provider

这个问题其实和 上路Flutter 深入理解BuildContext 文章中提到的例子一样。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: FlatButton(
              onPressed: () {
                Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => SecondPage()));
              },
              child: Text('跳转')),
        ),
      ),
    );
  }
}
复制代码

当我们点击「跳转」按钮,会报出异常,

flutter: Navigator operation requested with a context that does not include a Navigator.
flutter: The context used to push or pop routes from the Navigator must be that of a widget that is a 
flutter: descendant of a Navigator widget.
复制代码

这种错很好理解,Flutter 主流状态管理框架 provider get 分析与思考 中我们提到过,context 对应的是 Widget 在 Element 树中的节点,通过向上查找父节点中的 Presenter。如果使用的 context 过高,那自然无法获取。

image.png

这点使用 provider 也一样,知道原理之后解法也很简单,使用 Provider 之下的 context 即可。

2、context 已从树中移除

这一点我在实践中遇到过,一般的页面在打开时,显示 loading 进行请求,之后根据网络状态展示内容或者异常页面。如果展示异常我们会展示为一个「重新加载」页面,点击进行网络请求。

那次的问题在于,再次请求时网络状态成功了,页面展示为正常的状态。但是业务逻辑中还在用「重新加载」widget 对应的 context 获取 Provider。因为「重新加载」widget 已经从树中移除了,所以在使用结果自然报错。

image.png

这个问题看起来很简单,实际在第一次遇到时压根没有往这个方向联想,一步步 Debug 当前 context 所对应的 widget,以及查看他的 parent 节点最终才发现了这个问题。

总结一下,对于 ProviderNotFoundException 这一类的异常,往往是由于 context 对象错误导致。遇到的时候不要慌,抓住 Flutter 树机制的核心。Debug 当前的 context 对象,观察他的 widget、parent 等属性,看是否符合预期,答案往往就在里面。


二、get 中的一些实践问题

上一期我们提到,get 其实核心的设计在于将 Presenter 存到一个单例的 Map 中,这样在任何地方都能随时访问。 image.png

其中单例存储的 key 是 Presenter.runtimeType + tag tag 是一个 String 类型的标识,value 是对应的 Presenter 实例。

这种全局单例存储其实有个很蛋疼的问题,类型重复

1、类型重复

举个场景,淘宝商品详情页之间的跳转。

image.png 因为页面都是同一个类,只是不同实例。由于第一个页面已经将 Presenter 存放,这时如果你在第二个页面直接使用 Get.find<A> 会发现获取的还是上一个页面的 Presenter。

为了解决这个问题,我们必须要在 Get.put 的时候必须给每一个 Presenter 加上唯一 tag,比如商品 id。

image.png

但如果这样,获取 Presenter 时必须知道 tag,在跨页面访问 presenter 时如何共享 tag 又成了一个新的问题。

你可能会想 provider 中就没有这个问题了么?因为一般我们在使用 Provider 的时候,都是跟随页面的。相当于每个页面都有一个 Map 进行存储,打开不同的页面对应是不同的 Map,所以不用考虑类型重复的问题。

image.png

但是跨页面访问 Presenter 任然是个麻烦的问题。

2、Getbuilder<T>

最后这一点也是 fluttercandidate 中一个群友实际遇到的问题,也是我个人认为这个框架不合理的地方。就是 Getbuilder<T> 通过传递 Presenter 泛型在 builder 中将其返回使用。

GetBuilder<Presenter>(
   /// builder 中直接将 Controller 对应的实例返回
   builder: (presenter) => 
   Text('clicks: ${presenter.count}')
   )
复制代码

当时那个兄 dei,也是和上面一样商品详情页重复跳转的场景,为了解决类型重复的问题,他使用时间戳作为 tag。

final Presenter presenter = Get.put(Presenter(),tag: DateTime.now().toString());
复制代码

结果在使用 GetBuilder 的时候傻眼了:

GetBuilder<Presenter>(
   /// 和上面 tag 不是一个,所以查找不到实例
   tag: DateTime.now().toString(),
   builder: (presenter) => 
   Text('clicks: ${presenter.count}')
   )
复制代码

理解 Get 的注入之后,GetBuilder 的原理其实非常简单。因为是全局单例 Map 存储,所以只需要 key(runtimeType+tag) 就可以获取到对应的 Presenter,GetBuilder 相当于自动为我们做了查找这么一件事。这个例子中,因为 put 和 GetBuidler 的 tag 不一样(当前的时间戳),所以获取 GetBuilder 获取不到。只需要简单的将 tag 存起来在 GetBuilder 中使用即可。

但这个自动查找泛型在我看来并没有什么必要,完全可以参考系统的 ValueListenableBuilder 的做法,让使用者手动传入 Presenter 实例变成下面这样。

GetBuilder<Presenter>(
   /// 手动传入 presenter ,表示这个 presenter 与 GetBuilder 建立了监听关系。
   presenter: presenter,
   builder: (presenter) => 
   Text('clicks: ${presenter.count}')
   )
复制代码

这么对比起来看似多了一个参数 presenter,但这个 presenter 一定是你能提前获取的。手动的指定「监听对象」和「监听者」的绑定我认为会让开发者更加清楚当前这个 GetBuilder 监听的 presenter 是谁。这样压根从源头上避免了上面使用时间戳做 tag 的问题。


三、总结

以上便是我在实践中遇到的一些问题,其实本质还是由于他们不同的设计思路导致。例如,使用树机制的 provider,就肯定存在 context 异常的可能。而 get 使用全局单例的方式自然避免不了类型重复,或者回收一类的问题。所以对于框架,更重要的是去理解他的设计思路,而不是无脑的跟风。相关内容在上期也有提过:Flutter 主流状态管理框架 provider get 分析与思考,希望能帮助你避开一些坑。

状态管理相关内容到这期就结束了,下一部分我专注学习 Dart 虚拟机和 isolate 的相关内容,欢迎关注。

如果你有任何疑问可以通过公众号与联系我,如果文章对你有所启发,希望能得到你的点赞、关注和收藏,这是我持续写作的最大动力。Thanks~

公众号:进击的Flutter或者 runflutter 里面整理收集了最详细的Flutter进阶与优化指南,欢迎关注。

往期精彩内容:

Flutter 进阶优化

Flutter核心渲染机制

Flutter路由设计与源码解析

猜你喜欢

转载自juejin.im/post/7030614911407357959