Flutter结果通知插件result_notifier的使用

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

Flutter结果通知插件result_notifier的使用

质量生活增强插件 - 简单且适度的Flutter状态管理

result_notifier.png

Result Notifier 是一个简单的包,用于增强Flutter已经提供的内置状态管理。换句话说,它基于熟悉的平台原生概念,而不是引入新的抽象和思维模型。事实上,这个包其实只是对 ValueNotifierChangeNotifier 的一些扩展(好吧,可能不止一点,但也没什么疯狂的东西)。正如这个包的名字所暗示的,最重要的添加之一就是Result类型的概念,它可以表示某些数据、错误或加载状态。

style: lint

注意:v0.5.0 包含了破坏性更改,请参阅 changelog

Result Notifier 的本质

ResultNotifierValueNotifierChangeNotifier 不同的基本概念有四个:

  • 它持有一个 Result 值(一种代数数据类型),并提供了访问和修改该值的方法。该值(结果)可以处于三种不同的状态之一:

    • Data:结果包含某些具体数据。
    • Error:表示错误,并附带之前的任何数据。
    • Loading:表示加载/重新加载状态,并附带之前的任何数据。
  • 它是可刷新的 - 通过提供同步或异步的“获取器”函数,您可以指定在需要时(或数据过期时)如何刷新数据。

  • 它是可缓存的 - 通过设置 cacheDuration,您可以指定数据在被认为是过期之前应该被缓存多长时间。

  • 它是可组合的 - 您可以轻松地将多个 ResultNotifier 的数据(甚至其他 ValueListenable)组合在一起,例如使用 CombineLatestNotifier 或应用效果使用 EffectNotifier

Result 类型中的代数数据类型支持

如上所述,Result 是一种代数数据类型,这意味着您可以在 switch 语句中像这样使用它:

switch (result) {
  Data(data: var d, lastUpdate: var t) => Text(d),
  Error(error: var e, stackTrace: var s, data: var d, lastUpdate: var t) => Text('Error: $e'),
  Loading(data: var d, lastUpdate: var t) => const CircularProgressIndicator(), 
}

…或者简单地像这样(随你便):

switch (result) {
  (Data d) => Text(d.data),
  (Error e) => Text('Error: ${e.error}'),
  (_) => const CircularProgressIndicator()
}

开始使用

  1. 简单地 添加依赖 并开始编写一些通知器!
  2. 深入了解入门 示例 (更多信息 在这里) …或者 - 只需继续阅读以下快速介绍基本概念。👇

一个简单的开始

最简单的通知器形式只持有某个值,就像 ValueNotifier 一样。但是使用 ResultNotifier,值被包裹在一个 Result 类型中,可以表示某些数据、错误或加载状态。

final notifier = ResultNotifier<String>(data: 'Hello...');
print(notifier.data); // 打印 'Hello...'
print(notifier.result); // 打印 'Data<String>(data: Hello..., lastUpdate: 2024-01-02 03:04:05.000006)'

notifier.toLoading(); // 便捷方法来设置值为 Loading,保留之前的值。
// 举例说明如何使用只读属性获取当前通知器的状态:
notifier.isLoading;
notifier.isData;
notifier.isError;
notifier.hasData;
// 如果使用缓存 `expiration`,还可以检查数据是否新鲜或过期:
notifier.isFresh;
notifier.isStale;

// 使用实际数据类型直接设置新数据值(Data),替换之前的值/结果:
notifier.data = 'Hello Flutter!';
// 或者,使用新的 Result(在这种情况下为 Data)替换之前的值/结果:
notifier.value = Data('Hello Flutter!');
// 或者,使用 Future 设置值: 
notifier.future = Future.value('Hello again Flutter!');
// await notifier.future; // 可选地等待 Future 完成。

异步获取数据(例如从API)

通常你会想要做一些更复杂的事情,比如从API获取数据。在这种情况下,你可以使用 FutureNotifier(或 ResultNotifier.future),这是一个使用“获取器”函数返回 Future 的 ResultNotifier

final notifier = ResultNotifier<String>.future(
  (_) async {
    final response = await http.get(Uri.parse('https://www.boredapi.com/api/activity/'));
    final json = jsonDecode(response.body) as Map<String, dynamic>;
    return json['activity'] as String;
  },
  data: 'Test in production', // 可选地设置初始值。
  expiration: const Duration(seconds: 42), // 可选地设置缓存过期时间。
);

// 刷新数据(即调用获取器函数以更新通知器的数据):
notifier.refresh();

阅读更多关于缓存的部分。

观察(监听、监视…)变化

基于 ValueListenableBuilder 的观察

由于 ResultNotifier 实现了 ValueListenable,您可以简单地使用 ValueListenableBuilder 来观察小部件中的变化。但是,此包还提供了 ResultBuilder 上的方法 (builder),使这更方便。

notifier.builder((context, result, child) => switch (result) {
  Data(data: var d) => Text(d),
  Error(error: var e) => Text('Error: $e'),
  Loading() => const CircularProgressIndicator(),
}),

“钩子风格”的内联观察

如果你喜欢“钩子风格”构建小部件,你可以使用 watch 方法(通过扩展方法在 ResultNotifierValueNotifierChangeNotifier 等上可用)来观察 Listenable 的变化。虽然表面上类似于 flutter_hooks,但这种实现没有那些架构风格的一些复杂性,主要是因为范围较窄并且依赖现有的 ResultNotifier(或 Listenable)实例。

Watcher(builder: (context) {
  final result = notifier.watch(context);
  return Text(switch (result) {
    Data(data: var d) => d,
    Error(error: var e, data: var d) => 'Error - $d - $e',
    Loading(data: var d) => 'Loading - $d',
  });
});

这种类型的观察非常轻量级,特别是如果使用无状态的 WatcherWatcherWidget(也有状态化实现)。观察是通过使用 WatcherRef 接口实现的,该接口由 WatcherContext 实现(传给 WatcherbuilderWatcherWidgetbuild 方法等中的 BuildContext)。尽管 watch 方法是由 WatcherRef 提供的,但通常更方便使用 ResultNotifier(和 Listenable 等)上的扩展方法。

虽然使用 watch 很简单,但也有一些需要注意的地方:

  • watch 方法只能在 WatcherWidgetbuild 方法中或混合了 WatcherMixinStatefulWatcherMixin 的小部件中调用。
  • 当不再调用 watch 或小部件从树中移除(即处置)时,会自动处理处置。
  • build 方法中的条件逻辑与 watch 兼容,记住上面的要点 - 即小部件不会因为上次 build 调用中未观察到的 Listenable 而重建。

这种观察风格的一个额外好处是,它使得更容易观察多个 ListenableResultNotifier 的变化,而无需大量嵌套的 ValueListenableBuilderResultBuilder。例如:

Watcher(builder: (context) {
  final result1 = notifier1.watch(context);
  final result2 = notifier2.watch(context);
  final combined = [result1, result2].combine((data) => '${data[0]} and ${data[1]}');
  return Text(switch (result) {
    (Data<String> d) => d.data,
    (Loading<String> l) => 'Loading - ${l.data}',
    (Error<String> e) => 'Error - ${e.data} - ${e.error}',
  });
});

深入了解

缓存

当使用远程数据时,通常会缓存数据一段时间并在其过期时刷新。这可以通过设置 cacheDuration 来完成。但是记住,缓存是一切罪恶的根源,所以除非你确定真的需要它,并且在实现应用程序核心功能之后,否则不要启用它。

final notifier = ResultNotifier<String>.future(
  (_) async { ... },
  data: 'Test in production', // 可选地设置初始值。
  expiration: const Duration(seconds: 42), // 可选地设置缓存过期时间。
);

// 如果使用缓存 `expiration`,还可以检查数据是否新鲜或过期:
notifier.isFresh; // 返回 true
notifier.isStale; // 返回 false

// 刷新数据(即调用获取器函数以更新通知器的数据):
notifier.refresh(); // 在这种情况下,只有在刷新调用后经过 42 秒,数据才会更新。

效果

你也可以使用效果(见 EffectNotifier),以构建更复杂的链式通知器:

final notifier = ResultNotifier<String>(data: 'Þetta er frábært!');
final effect = notifier.effect((_, input) => input.toUpperCase());
effect.onData(print);
notifier.data = 'Þetta er frábært!'; // 打印: "ÞETTA ER FRÁBÆRT!"

请参阅 这里 的效果示例以获得这些概念的更完整的演示。

内联效果

使用钩子风格的方法观察变化的一个优点是,您可以轻松地组合多个通知器的结果(无需创建一个新的通知器)。这种效果既通过 Iterable 的扩展方法也通过一系列 Record 定义(例如 ResultTuple)来支持。

Widget build(BuildContext context) {
  final activity = activityRepository.watch();
  final counter = experiencePoints.watch();

  /// 这里我们使用 `combine` 或 `combineData` 扩展方法(定义在 `ResultTuple` 中)组合两个通知器的数据。
  final resultFromRecord = (activity, counter).combine((a, b) => '$a - total experience points: $b');

  /// 你也可以使用类似的功能作为 Iterable 的扩展方法。
  final resultFromList = [activity, counter].combine((data) => '${data[0]} - count: ${data[1]}');

  /// 或者如果你只需要数据:
  final resultData = (activity, counter).combineData((a, b) => '$a - count: $b');
}

更多关于Flutter结果通知插件result_notifier的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter结果通知插件result_notifier的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用result_notifier插件的示例。这个插件通常用于在不同的组件或页面之间传递结果或状态变化。假设你已经在pubspec.yaml文件中添加了result_notifier依赖,并运行了flutter pub get来安装它。

首先,确保你的pubspec.yaml文件中包含以下依赖项:

dependencies:
  flutter:
    sdk: flutter
  result_notifier: ^最新版本号 # 请替换为实际可用的最新版本号

接下来,我们可以创建一个简单的示例来展示如何使用result_notifier

1. 创建一个ResultNotifier实例

在需要传递结果的地方(例如,一个按钮点击事件中),我们可以创建一个ResultNotifier实例并发送结果。

2. 监听ResultNotifier的变化

在接收结果的地方(例如,另一个页面或组件),我们可以监听这个ResultNotifier的变化并处理结果。

示例代码

main.dart

import 'package:flutter/material.dart';
import 'package:result_notifier/result_notifier.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ResultNotifierExample(),
    );
  }
}

class ResultNotifierExample extends StatefulWidget {
  @override
  _ResultNotifierExampleState createState() => _ResultNotifierExampleState();
}

class _ResultNotifierExampleState extends State<ResultNotifierExample> {
  final ResultNotifier<String> resultNotifier = ResultNotifier<String>();

  void _sendResult(String result) {
    resultNotifier.postResult(result);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Result Notifier Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Send a result to the ResultNotifier:'),
            ElevatedButton(
              onPressed: () => _sendResult('Hello, ResultNotifier!'),
              child: Text('Send Result'),
            ),
            ResultNotifierListener<String>(
              notifier: resultNotifier,
              builder: (context, result, hasResult) {
                return Column(
                  children: [
                    if (hasResult)
                      Text('Received Result: $result'),
                    else
                      Text('No result received yet.'),
                  ],
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

解释

  1. ResultNotifier实例

    • 我们创建了一个ResultNotifier<String>实例,用于传递字符串类型的结果。
  2. 发送结果

    • 当点击按钮时,调用_sendResult方法,该方法使用postResult方法发送结果到ResultNotifier
  3. 监听结果

    • 使用ResultNotifierListener组件监听resultNotifier的变化。当结果变化时,builder方法会被调用,并传递当前的结果和是否有结果的布尔值。

这样,你就可以在Flutter应用中使用result_notifier插件来在不同组件或页面之间传递结果或状态变化。这个示例展示了基本的用法,你可以根据实际需求进行扩展和修改。

回到顶部