Day 26 | 共享 MobX store with get_it

还记得我们很早之前说过Flutter有一个问题就是嵌套太多层时我们要从下层拿到上层的东西时会变得十分麻烦。

换句话说也就是下层依赖於上层的物件/实例,但什麽时候会发生这种状况呢?其实在用MobX不知道大家有没有一个疑问「要怎麽共用store的资料」,毕竟我们不可能在另外一个widget重新实例化这个store 这样子就只是「两个同样类型的store,而不是同一个store」

对於这类问题 Flutter 的提供了 InheritedWidget

InheritedWidget

我们宣告一个 widget 继承 InheritedWidget 然後宣告data以及child 来放我们的资料以及widget。

class DataWidget extends InheritedWidget {
  const DataWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(child: child, key: key);
  final int data;
  static DataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<DataWidget>();
  }

  @override
  bool updateShouldNotify(DataWidget old) {
    return old.data != data;
  }
}
// main.dart 

// 省略

DataWidget(
  data: count,
  child: Column(
    children: [
      _Foo(),
      GestureDetector(
        child: Text('add 1'),
        onTap: () {
          setState(() {
            count++;
          });
        },
      ),
    ],
  ),
),

// 省略

class _Foo extends StatefulWidget {
  _Foo({Key? key}) : super(key: key);

  @override
  __FooState createState() => __FooState();
}

class __FooState extends State<_Foo> {

  @override
  Widget build(BuildContext context) {
    return Text(DataWidget.of(context)!.data.toString());
  }
}

然後在宣告一个子widget来取得 DataWidget 的资料。这里会发现我只要在 _Foo 里面用 DataWidget.of(context)!.data 就好,不必从参数传入任何资料,而在要显示_Foo 的地方记得要wrap DataWidget 并且将count 传入 data

从这个例子我们就能看出如果我们要共享一些资料可以不用传递参数给子widget 而是能利用InheritedWidget 减少widget与widget之间的耦合关系。但InheritedWidget 也有很明显的缺点:用起来很麻烦,除了需要实作InheritedWidget 的静态方法,使用时还要wrap InheritedWidget

所以大多数时候都会使用其他套件来解决这件事情。

get_it

get_it 是一个实现 service locator pattern 的套件,service locator可以将我们一些实例或者该说服务与我们的widget实作切开来,等我们真正需要使用这些服务时再将其取出。

首先我们先来安装get_it

dependencies:
	get_it: ^7.2.0

宣告 inject ,这里顺便注册了一个Singleton实例 UsersViewModel

final GetIt inject = GetIt.I;

Future<void> setupInjection() async {
  inject.registerLazySingleton<UsersViewModel>(() => UsersViewModel());
}

要到我们的widget使用时,先在 mainsetupInjection

void main() {
  setupInjection();
  runApp(MyApp());
}

然後到我们真正要取出服务的地方

class UserCard extends StatelessWidget {
  UserCard({Key? key, required this.userInfo}) : super(key: key);
  final Users userInfo;

  final usersViewModel = inject<UsersViewModel>();

	 //省略不重要得 widget宣告
	 //省略不重要得 widget宣告
	 //省略不重要得 widget宣告

  GestureDetector(
    onTap: () {
      usersViewModel.fetchSeledtedUser(userInfo.id.toString());
      AutoRouter.of(context)
          .push(UserDetailRoute(userId: userInfo.id.toString()));
    },
        
	 //省略不重要得 widget宣告
	 //省略不重要得 widget宣告
	 //省略不重要得 widget宣告

}

class UserDetailPage extends StatefulWidget {
  const UserDetailPage({Key? key, @PathParam('id') required this.userId})
      : super(key: key);
  final String userId;
  @override
  State<UserDetailPage> createState() => _UserDetailPageState();
}

class _UserDetailPageState extends State<UserDetailPage> {
  final usersViewModel = inject<UsersViewModel>();

  // @override
  // void initState() {
  //   usersViewModel.fetchSeledtedUser(widget.userId);
  //   super.initState();
  // }

 // 省略以下
	  
}

在昨天的 UserDetailPage 我们是使用 url param 来达成路由切换时也能拿到正确 userid ,今天我们将 UserCard 切换路由时顺便做一个 action 去 fetch 使用者资料。

并在 UserDetailPage 中使用一样是 inject 来的 usersViewModel 的资料,会发现我们一样能取得正确的使用者资料。

这样比较下来就能看出 get_it 比InheritedWidget 使用上方便很多,关於get_it其他用法这篇文章就不另外说明了。


今天的程序码

https://github.com/zxc469469/flutter_rest_api_playground/tree/Day26

我们之前在说「状态管理」时提到的一个名词「依赖注入」(dependency injection ,DI),DI 与service locator都是实作 IoC (Inversion of Control,控制反转) 的pattern 。

在大部分的解释上「依赖注入」是指我们利用一些方法将我们的依赖不是在我们要使用的类别中实例化而是从外部注入进来,进而让各类别解耦。

但在 Flutter 的相关文章或讨论时有看过有人是这麽解释的:

上层的「依赖」「注入」到很深很深的下层

某种程度上也算符合原本定义因为我的确也不是在子层widget去实例化我们的依赖,但可能是因为 Flutter 我们更聚焦在解决底层widget取用上层资料时所遇到的麻烦。

所以会导致很多东西会被误为是DI 但其实是 service locator ,详细的讨论可以阅读参考资料中的第三篇。


参考资料

  1. Inheritedwidget
    https://book.flutterchina.club/chapter7/inherited_widget.html
  2. get_it
    https://medium.com/nerd-for-tech/implement-service-locator-design-pattern-with-get-it-flutter-5e50671bbbcb
  3. flutter中的依赖注入
    https://ithelp.ithome.com.tw/articles/10235302
  4. service locator vs di
    https://medium.com/@ivorobioff/dependency-injection-vs-service-locator-2bb8484c2e20

<<:  初学者跪着学JavaScript Day24 : 原型不会,但你还有class

>>:  [第24天]理财达人Mx. Ada-RSI指标

一分钟的思考,远胜於一个小时的谈话。

一分钟的思考,远胜於一个小时的谈话。 A minute of thought is greater ...

DAY11 制作样板(Template)

用这个来开发样板 https://developers.line.biz/flex-simulato...

[DAY-11] 诚实敢言最大化 建立回馈循环

只说你敢当面对那个人说的话 越少在背後议论别人 会妨碍效率且引起负面感受的八卦就会减少 诚实就像看...

29.MYSQL 资料统整3

继续帮大家整理资料统整的语法 建立一张资料表如下 MAX :栏位最大值 用法: SELECT MAX...

Day06 UIKit 05 - 纯代码编写 Code

在开发前我们需要知道,在 iOS 中,我们可以采取 Storyboard、XIBs 或是纯 Code...