Flutter服务定位与钩子插件get_it_hooks的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter服务定位与钩子插件get_it_hooks的使用

本指南将帮助你了解如何在Flutter项目中使用get_it_hooks插件。get_it_hooksflutter_hooks提供了一组钩子,使得存储在GetIt中的数据绑定变得非常简单。

get_it_hooks

此包提供了一组钩子,用于Remi Rousselet的flutter_hooks包,使得存储在GetIt中的数据绑定变得非常容易。

当我提到绑定时,我指的是一个机制,该机制会在依赖的数据发生变化时自动重建小部件。

这些钩子提供了与get_it_mixin相同的功能。你可能会问为什么还需要钩子?原因是flutter_hooksget_it_mixin都试图覆盖小部件的createElement()函数,这意味着你不能同时使用mixin和任何钩子。

开始使用

模型类创建

首先,我们创建一个模型类,我们希望用钩子来访问它:

class Model extends ChangeNotifier {
  String _country;
  set country(String val) {
    _country = val;
    notifyListeners();
  }
  String get country => _country;

  String _emailAddress;
  set emailAddress(String val) {
    _emailAddress = val;
    notifyListeners();
  }
  String get emailAddress => _emailAddress;

  final ValueNotifier<String> name;
  final Model nestedModel;

  Stream<String> userNameUpdates; 
  Future get initializationReady;
}

接下来,我们将展示如何使用get_it_hooks访问不同的属性。要使这有效,你需要在依赖项中添加flutter_hooksget_it_hooks

数据读取

所有钩子都以use...开头。最简单的两个是useGet()useGetX(),它们从GetIt中获取数据,就像直接使用GetIt.I&lt;Type&gt;()一样。

class TestStateLessWidget extends HookWidget {

  @override
  Widget build(BuildContext context) {
    final email = get&lt;Model&gt;().emailAddress;
    return Column(
      children: [
        Text(email),
        Text(useGetX((Model x) =&gt; x.country, instanceName: 'secondModell')),
      ],
    );
  }
}

如上所示,useGet()的用法与直接使用GetIt完全相同,包括所有参数。useGetX()也做同样的事情,但它提供了一个选择器函数,该函数必须返回引用对象的最终值。大多数情况下,你可能只会使用useGet(),但选择器函数可以用来处理任何在使用值之前可能需要的数据处理。

useGet()useGetX()可以在小部件内部和外部多次调用,并且可以在build()函数之外调用。因为它们不是真正的钩子,而只是方便的函数。

监视数据

以下函数将返回一个值,并且每次GetIt中的数据更改时都会重新构建小部件。

重要:所有以下函数只能在build()函数内调用。此外,所有这些函数必须始终以相同的顺序在每个build上调用,否则钩子会混乱。

假设你在GetIt中注册了一个实现ValueListenableBuilder&lt;String&gt;的对象,名为currentUserName,并且我们希望上述小部件在它的值更改时重新构建。我们可以这样做,通过添加一个ValueListenableBuilder

class TestStateLessWidget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ValueListenableBuilder&lt;String&gt;(
          valueListenable: GetIt.I&lt;ValueListenable&lt;String&gt;&gt;(instanceName: 'currentUser'),
          builder: (context, val,_) {
            return Text(val);
          }
        ),
      ],
    );
  }
}

使用钩子,我们现在可以这样写:

class TestStateLessWidget1 extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final currentUser = 
       useWatch&lt;ValueListenable&lt;String&gt;, String&gt;(instanceName: 'currentUser');

    return Column(
      children: [
         Text(currentUser)
      ],
    );
  }
}

不幸的是,我们需要提供第二个泛型参数,因为Dart无法推断返回值的类型。幸运的是,我们将在以下函数中看到有一种方法可以帮助编译器。

使用useWatchX

在实际应用中,你的业务对象更有可能不是ValueListenable本身,而是它的一些属性可能是ValueListenables,例如我们的Model类中的name属性。为了对这种属性的变化做出反应,你可以使用useWatchX()

class TestStateLessWidget1 extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final name = useWatchX((Model x) =&gt; x.name);
    /// 如果监听器嵌套更深
    final innerName = useWatchX((Model x) =&gt; x.nestedModel.name);

    return Column(
      children: [
        Text(name),
        Text(innerName),
      ],
    );
  }
}

这个小部件将在任何一个被监视的ValueListenables发生变化时重建。

你可能会好奇为什么我没有在useWatchX()中传递Model作为泛型参数。原因是其签名如下:

R useWatchX&lt;T, R&gt;(
    ValueListenable&lt;R&gt; Function(T x) select, {
    String instanceName,
  }) =&gt;

这意味着你必须传递两个泛型类型,不仅仅是T,还有R。如果你在select函数中传递了T,编译器就能推断出R

使用useWatchOnly & useWatchXonly

另一种常见的模式是业务对象实现Listenable,比如ChangeNotifier,并且当其属性之一发生变化时会通知其监听者。由于我们只想在需要更新的值改变时重新构建小部件,useWatchOnly()允许你定义想要观察的属性,并且只有在它真正变化时才会触发重建。

useWatchXonly()做同样的事情,但对于嵌套的Listenables

class TestStateLessWidget1 extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final country = useWatchOnly((Model x) =&gt; x.country);
    /// 如果被监视的属性嵌套更深
    final innerEmail = useWatchXOnly((Model x) =&gt; x.nestedModel, (Model o) =&gt; o.emailAddress);

    return Column(
      children: [
        Text(country),
        Text(innerEmail),
      ],
    );
  }
}

这个小部件将在Model对象的country或嵌套ModelemailAddress发生变化时重建。如果你更新ModelemailAddress,它不会更新,尽管它也调用了notifyListeners

如果你想在Model触发notifyListeners时得到更新,你可以通过这个选择器方法实现:

final model = useWatchOnly((Model x) =&gt; x);

流和未来

如果你希望在模型中的流发出新值或未来完成时更新你的小部件,你可以使用useWatchStreamuseWatchFuture。好的地方在于你不必关心取消订阅,钩子会为你处理。所以,而不是使用StreamBuilder,你可以直接这样做:

class TestStateLessWidget1 extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final currentUser = useWatchStream((Model x) =&gt; x.userNameUpdates, 'NoUser');
    final ready =
        useWatchFuture((Model x) =&gt; x.initializationReady, false).data;

    return Column(
      children: [
        if (ready != true || !currentUser.hasData) // 在发生错误时,ready 可能为null
          CircularProgressIndicator()
        else
          Text(currentUser.data),
      ],
    );
  }
}

这些函数可以处理如果选择器函数在后续的build调用中返回不同的流和未来的场景。在这种情况下,旧的订阅会被取消,并且新的Stream会被订阅。更多详情请查看API文档。

事件处理器

也许你不需要更新值,而是想在Stream发出值或ValueListenable更新值或Future完成时显示一个Snackbar。如果你不想使用mix-in来实现这一点,你将需要一个StatefulWidget,在initState中订阅一个Stream并在Statedispose函数中取消订阅。

使用钩子,你可以为StreamsValueListenablesFutures注册处理器,钩子会在小部件销毁时为你清理一切。

class TestStateLessWidget1 extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    /// 注册一个处理器,用于valueListenable
    useRegisterHandler((Model x) =&gt; x.name, (context, name, _) =&gt; showNameDialog(context, name));
    
    useRegisterStreamHandler((Model x) =&gt; x.userNameUpdates, (context, name, _) =&gt; showNameDialog(context, name));

    useRegisterFutureHandler((Model x) =&gt; x.initializationReady, (context, __, _) =&gt; Navigator.of(context).push(...));
    return Column(
      children: [
        //... whatever widgets needed
      ],
    );
  }
}

例如,当你使用useWatch()获取值时,你可以注册一个处理器,用于flutter_commandthrownExceptions

在上面的例子中,你可以看到处理器函数有第三个参数,我们忽略了它。你的处理器会收到一个处置函数,该函数可以在自身内部使用以注销注册。

使用useAllReady() 和 useIsReady()

如果你已经使用了GetIt中的同步函数,你应该知道这两个函数(否则请参阅GetIt文档)。钩子版本返回实际状态作为bool值,并在状态更改时触发重建。此外,你可以注册处理器,在状态为true时调用它们。

class TestStateLessWidget1 extends HooksWidget {
  @override
  Widget build(BuildContext context) {
    final isReady = useAllReady();

    if (isReady) {
      return MyMainPageContent();
    } else {
      return CircularProgressIndicator();
    }
  }
}

或者使用处理器:

class TestStateLessWidget1 extends Widget {
  @override
  Widget build(BuildContext context) {
    useAllReady(
      onReady: (context) =&gt; Navigator.of(context).pushReplacement(MainPageRoute())
    );

    return CircularProgressIndicator();
  }
}

useIsReady&lt;T&gt;()可以用相同的方式使用,以响应单个异步单例的状态。

推入一个新的GetIt作用域

使用usePushScope(),你可以推入一个作用域,当小部件/状态被销毁时会被弹出。你可以传递一个init函数,该函数将在作用域推入后立即调用,以及一个可选的dispose函数,该函数将在作用域弹出前直接调用。

void usePushScope({void Function(GetIt getIt) init, void Function() dispose});

更多关于Flutter服务定位与钩子插件get_it_hooks的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter服务定位与钩子插件get_it_hooks的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,我可以为你提供一个关于如何在Flutter中使用get_it_hooks插件进行服务定位和钩子(hooks)管理的示例代码。get_it_hooks是一个Flutter依赖注入库get_it的扩展,它允许你在获取依赖项时执行自定义逻辑。

首先,确保你在pubspec.yaml文件中添加了get_itget_it_hooks依赖:

dependencies:
  flutter:
    sdk: flutter
  get_it: ^7.2.0  # 请检查最新版本
  get_it_hooks: ^2.0.0  # 请检查最新版本

然后,运行flutter pub get来安装这些依赖。

接下来,我们编写一些示例代码来展示如何使用这些库。

1. 设置依赖注入容器

首先,我们需要创建一个GetIt实例并注册一些服务。

import 'package:get_it/get_it.dart';
import 'package:get_it_hooks/get_it_hooks.dart';

final getIt = GetIt.instance;

class MyService {
  String message = "Hello, World!";
}

void setupDependencies() {
  getIt.registerSingleton<MyService>(MyService());
  
  // 添加一个钩子,在每次获取MyService实例之前执行
  getIt.hookFor<MyService>().beforeGet((context) {
    print("Before getting MyService instance");
  });

  // 添加一个钩子,在每次获取MyService实例之后执行
  getIt.hookFor<MyService>().afterGet((context, instance) {
    print("After getting MyService instance with message: ${instance.message}");
  });
}

2. 使用服务

然后,我们可以在应用程序的其他部分使用这些服务,并观察钩子的执行。

import 'package:flutter/material.dart';
import 'your_dependency_setup_file.dart';  // 假设上面的代码保存在这个文件里

void main() {
  // 设置依赖
  setupDependencies();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('GetIt Hooks Example'),
        ),
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 获取服务并显示消息
    final myService = getIt.get<MyService>();

    return Text(myService.message);
  }
}

3. 运行应用

运行你的Flutter应用,你应该会在控制台中看到钩子在获取MyService实例之前和之后打印的消息。

Before getting MyService instance
After getting MyService instance with message: Hello, World!

总结

以上代码展示了如何在Flutter中使用get_itget_it_hooks来设置和管理依赖项,以及如何在获取依赖项之前和之后执行自定义逻辑。通过这种方式,你可以轻松地在Flutter应用中实现依赖注入和钩子功能。

回到顶部