Flutter值扩展功能插件value_extensions的使用

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

Flutter值扩展功能插件value_extensions的使用

value_extensions 是一组扩展功能,它们在 ValueListenable 上提供了类似于 Rx 的流操作。这个包包含转换器、便捷对象和方法,以及可以将所有这些实体集成到小部件中的小部件。

动机

在 Flutter 中,有两种 PubSub 方法用于小部件:StreamListenableStream 被广泛使用,在某些情况下非常有用,并且功能强大,但代价是大量的样板代码。

要拥有一个具有获取其最后一个值的 getter 的状态化 Stream,需要执行以下步骤:

  1. 创建一个 StreamController
  2. 暴露一个 Stream
  3. 创建一个可变缓存变量
  4. 暴露该变量的 getter
  5. 每次缓存值发生变化时,必须将新值添加到 Sink

此外,要在小部件中通过 StreamBuilder 使用它,还需要提供 stream 参数和 initialValue 参数。

这需要大量的代码。

另一方面,ValueListenable 更简单,需要的代码更少。但是,它们缺乏 Stream 的功能——很难创建派生通知器、自动处理取消订阅、订阅和取消订阅,以及在不进行疯狂嵌套的情况下将其集成到 UI 中。Value Extensions 解决了这个问题。

开始使用

以下是所有扩展及其用例的列表。有关更多信息,请参阅 API 参考或示例。

工具扩展

这些扩展提供基础功能:创建、设置、订阅和处理通知器。

设置

.set(...) 扩展提供了一个更好的 API 来设置 ValueNotifier 的值。使用 .set 方法更简洁,并且更好地表达了意图。以下两个示例是等效的:

someNotifier.value = 10;

someNotifier.set(10);

更新

.update((current) => ...) 扩展提供了一个更好的 API 来设置 ValueNotifier 的值,当新值依赖于旧值时。

例如:

int timesTwo(int x) => x * 2;

/// 传统方式
someNotifier.value = timesTwo(someNotifier.value);

/// 扩展方式
someNotifier.update(timesTwo);

订阅

ValueListenable 提供了内置的订阅功能,但获取其状态和取消订阅有些棘手。此扩展提供了这些情况的功能。

final subscription = someValueListenable.subscribe(print);

print(subscription.isCanceled); // 打印 'false'
someValueListenable.set((_) => 10); // 打印 10

subscription.cancel();

print(subscription.isCanceled); // 打印 'true'
someValueListenable.set((_) => 100); // 不打印

还可以暂停和恢复订阅。

提取值

有时,需要将 Stream 转换为 ValueListenable。此扩展正是为此而设计的。

final stream = Stream.periodic(Duration(seconds: 1), (second) => second);
final secondsPassed = stream.extractValue(initial: 0);

转换器

接下来的部分重点介绍了 ValueListenables 的转换。它们实现了迭代器模式并复制了 Stream 的方法,因此当基本 ValueListenable 发生变化时,派生的 ValueListenables 也会随之变化。

映射

创建一个新的 ValueListenable,从基本的 ValueListenable 中派生其值。

final stringNotifier = ValueNotifier('Hello');
final lengthListenable = stringNotifier.map((value) => value.length); // 5
stringNotifier.update((hello) => '$hello, World!'); // lengthNotifier 值变为 13

平展映射

类似于 map,但转换函数必须返回另一个 ValueListenable 而不是常规值,每当调用 flatMapValueListenable 发生变化时,都会重新计算其值。

final intNotifier = ValueNotifier(0);

final stringNotifier = ValueNotifier('Hello!');
final lengthListenable = stringNotifier.map((string) => string.length);

final isShowingInt = ValueNotifier(true);

final currentValue = isShowingInt.flatMap((isInt) => isInt ? intNotifier : lengthListenable);

过滤

通过过滤基本 ValueListenable 的值来创建新的 ValueListenable,不包括第一个值。

final intNotifier = ValueNotifier(1);

final evenListenable = intNotifier.where((value) => value.isEven); // 1
final oddListenable = intNotifier.where((value) => value.isOdd); // 1

// evenListenable: 2
// oddListenable: 1
intNotifier.update((value) => value + 1);

最新组合

通过将 other 的值与其自身的值结合,并将这对值传递给转换函数来创建新的 ValueListenable

final word = ValueNotifier('Flutter');
final isUppercased = ValueNotifier(false);

final textListenable = word.combineLatest<bool, String>(
  isUppercased, 
  (word, isUppercased) => isUppercased ? word.toUpperCase() : word,
); // Flutter

isUppercased.set(true); // textNotifier 值变为 FLUTTER

并行

这是对 .combineLatest(...) 扩展的包装。将 ValueListenable 的最新值打包在一个元组中。在小部件中避免嵌套的 ValueListenableBuilder.bind 调用时非常有用。

final intNotifier = ValueNotifier(0);
final boolNotifier = ValueNotifier(false);

final paralleled = intNotifier.parallelWith(boolNotifier); // (0, false)

intNotifier.update((current) => current + 1); // paralleled 变为 (1, false)
boolNotifier.set(true); // paralleled 变为 (1, true)

小部件集成

最后一部分重点介绍小部件的集成。

StreamsValueListenable 都旨在通过构建器在 UI 中显示。唯一的问题是——构建器相当冗长。为了“绑定”一个可观察变量到某个小部件,需要输入大约 86 个字符。Value Extensions 将这个数字减少到了大约 23 个字符,几乎减少了 4 倍!

绑定

要将 ValueListenable 绑定到小部件,使用 .bind(...) 扩展。

考虑这两个示例:使用 ValueListenableBuilder.bind(...)

ValueListenableBuilder(
  valueListenable: nameNotifier,
  builder: (context, name, child) => Text("Hello, $name!"),
)
nameNotifier.bind(
  (name) => Text("Hello, $name!"),
)

.bind(...) 扩展内部使用 ValueListenableBuilder,因此结果是相同的有效目标重建,但需要输入的代码更少。

注意:在 .parallelWith(...) 扩展获得的 ValueListenable 上使用的 .bind(...) 扩展会自动解构其值在生成器回调中。

完整示例代码

以下是一个完整的示例代码,展示了如何使用 value_extensions 插件。

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:value_extensions/value_extensions.dart';

class StateObject {
  /// 基础私有通知器,其他所有通知器都从中派生其值。
  final _counter = ValueNotifier(0);

  late final StreamValueListenable<int> _secondsPassed =
      _timer.extractValue(initial: 0);

  /// 基本流用于转换演示
  final _timer =
      Stream.periodic(const Duration(seconds: 1), (second) => second);

  /// 使用 [where()] 和 [subscribe()] 扩展的派生订阅
  late final Subscription evenPrintSubscription;

  StateObject() {
    evenPrintSubscription =
        _counter.where((value) => value.isEven).subscribe(print);
  }

  ValueListenable<Color> get counterColor =>
      _counter.map((value) => value.isEven ? Colors.red : Colors.blue);

  ValueListenable<String> get stringCounterValue =>
      _counter.map((value) => value.toString());

  ValueListenable<int> get secondsPassed => _secondsPassed;

  List<ChangeNotifier> get _disposable => [_counter, _secondsPassed];

  void increment() => _counter.update((value) => value + 1);

  void dispose() {
    evenPrintSubscription.cancel();
    _disposable.disposeAll();
    print("Disposed $this");
  }
}

class CounterScreen extends StatefulWidget {
  [@override](/user/override)
  CounterScreenState createState() => CounterScreenState();
}

class CounterScreenState extends State {
  final state = StateObject();

  [@override](/user/override)
  void dispose() {
    state.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              state.secondsPassed.bind(
                (seconds) => Text("Seconds passed: $seconds"),
              ),
              const SizedBox(height: 100),
              state.stringCounterValue.parallelWith(state.counterColor).bind(
                    (text, color) => Text(
                      text,
                      style: TextStyle(color: color),
                    ),
                  ),
              OutlinedButton(
                onPressed: state.increment,
                child: const Text("Increment"),
              ),
              OutlinedButton(
                onPressed: state.evenPrintSubscription.cancel,
                child: const Text("Cancel print"),
              ),
              OutlinedButton(
                onPressed: Navigator.of(context).pop,
                child: const Text("Navigate back"),
              ),
            ],
          ),
        ),
      );
}

class Home extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text("Home")),
        body: Center(
          child: OutlinedButton(
            onPressed: () => Navigator.push<void>(
              context,
              MaterialPageRoute(builder: (context) => CounterScreen()),
            ),
            child: const Text("Navigate to Counter"),
          ),
        ),
      );
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) => MaterialApp(
        home: Home(),
      );
}

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

更多关于Flutter值扩展功能插件value_extensions的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter值扩展功能插件value_extensions的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用value_extensions插件的示例代码。value_extensions插件通常用于为常见的数据类型(如intdoubleString等)添加一些实用的扩展函数。假设你已经在pubspec.yaml文件中添加了该插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  value_extensions: ^最新版本号  # 请替换为当前最新版本号

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

示例代码

以下是一个简单的Flutter应用,展示了如何使用value_extensions插件中的一些扩展函数。

main.dart

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Value Extensions Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Value Extensions Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Int extension example: ${42.isEven}',
                style: TextStyle(fontSize: 20),
              ),
              SizedBox(height: 20),
              Text(
                'Double extension example: ${3.14.roundTo(2)}',
                style: TextStyle(fontSize: 20),
              ),
              SizedBox(height: 20),
              Text(
                'String extension example: "${"hello".capitalize}"',
                style: TextStyle(fontSize: 20),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

解释

  1. Int 扩展:

    • 42.isEven:检查整数是否为偶数。value_extensions插件为整数类型添加了诸如isEvenisOdd等实用函数。
  2. Double 扩展:

    • 3.14.roundTo(2):将浮点数四舍五入到指定的小数位数。这里将3.14四舍五入到小数点后两位。
  3. String 扩展:

    • "hello".capitalize:将字符串的首字母大写。这里将"hello"转换为"Hello"

注意事项

  • 请确保你已经安装了最新版本的value_extensions插件。
  • 根据插件文档,可能会有更多的扩展函数可用,这里只展示了几个常见的例子。
  • 插件的功能可能会随着版本更新而变化,因此建议查看插件的官方文档以获取最新信息和完整的功能列表。

通过这种方式,你可以在Flutter项目中轻松地使用value_extensions插件提供的各种实用扩展函数,从而提高代码的可读性和简洁性。

回到顶部