flutter ValueNotifier and ValueListenableBuilder

928 阅读6分钟

Flutter 中的状态

在 Flutter 中,一切都是 widget 。 Flutter 在每个给定时间维护应用程序的当前状态,因此每当我们更新用户界面时,我们就说我们正在更新状态。

Flutter 有自己的状态管理系统,Stateful Widget 中流行的 setState 函数,它在调用时执行 build 方法。默认情况下,程序遵循其生命周期,但当调用 setState 时,程序会重新运行 build 方法并重建 widget 树。

image.png

在大多数情况下使用 setState 是合适的。如果使用不当,setState 可能会出现一些问题:

  • 前端和后端之间的紧密耦合。
  • 它再次重建整个 widget ,这可能既昂贵又耗时。

这就是为什么今天我们要研究另一种使用 ValueNotifier 更新状态的方法。它用于我们想要实现选择性重建的情况,这是大多数应用程序设计中的情况。

Flutter 中的 ValueNotifier 介绍

ValueNotifier 是一种特殊类型的类,它扩展了 ChangeNotifier。 ValueNotifier 是 Flutter 本身原生的,它提供了单个元素的响应性。 Flutter 默认还有一个 widget ,使用它我们可以监听名为 ValueListenableBuilder 的 ValueNotifier。也可以使用 AnimationBuilder 来监听,因为它是可监听的。

将 ValueNotifier 视为保持某个值的数据流。我们为它提供一个值,每个侦听器都会收到值更改的通知。

我们可以创建任何类型 int、bool、list 或任何自定义数据类型的 ValueNotifier。你可以像这样创建一个 ValueNotifier 对象:

ValueNotifier<int> counter = ValueNotifier<int>(0);

我们可以像这样更新值:

counter.value = counter.value++; 
//OR 
counter.value++;

另外,我们可以像这样监听 ValueNotifier:

counter.addListener((){
   print(counter.value); 
});

如果我们想监听 widget 中的值,我们使用 ValueListenableBuilder widget ,该 widget 开箱即用,负责在视图之外删除订阅。因此,当使用 ValueListenableBuilder 时,我们不需要手动添加和删除侦听器,它会自动为我们完成。

在 Flutter 中移除 Value Notifier Listener

如果我们手动收听 ValueNotifier,并且我们不释放它,因为它在应用程序的某个地方需要。当当前页面不使用时,我们可以使用 removeListener 函数从 ValueNotifier 中手动删除监听器。

ValueNotifier<int> valueNotifier = ValueNotifier(0); 

void removeDemo() { 
    valueNotifier.removeListener(doTaskWhenNotified); 
}

void addDemo(){ 
    valueNotifier.addListener(doTaskWhenNotified); 
}

void doTaskWhenNotified() { 
    print(valueNotifier.value); 
}

在 Flutter 中处理 ValueNotifier

最好在不再使用时释放值通知器,否则可能会导致内存泄漏。通知器上的 dispose 方法将释放任何订阅的侦听器。

@override void dispose() { 
    counter.dispose(); 
    super.dispose(); 
}

什么是 ValueListenableBuilder?

Flutter 中有许多类型的构建器,例如 StreamBuilder、AnimatedBuilder、FutureBuilder 等。它们的名字表明它们使用的对象类型。 ValueListenableBuilder 使用 ValueNotifier 对象。每次我们收到值更新时都会执行 builder 方法。当我们路由到另一个页面时,ValueListenableBuilder 会在内部自动删除监听器。

const ValueListenableBuilder({ 
    @required this.valueListenable, @required this.builder, this.child, 
})

这是 ValueListenableBuilder 的构造函数。这里,valueListenable 是要监听的 ValueNotifier。 builder 函数接收 3 个参数(BuildContext context, dynamic value, Widget child)。该值是从提供的 valueNotifier 接收到的数据。可以使用 child 参数。如果孩子的构建成本很高并且不依赖于通知器的值,我们将使用它进行优化。

在 Flutter 中使用 Value Notifier 的 Counter App

使用 ValueNotifer 和 ValueListenableBuilder 的计数器应用程序如下所示。请注意如何没有使用 setState,并且我们仅重建 Text 部分以防值更改。

import 'package:flutter/material.dart';

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

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

class _MyAppState extends State<MyApp> {
  ValueNotifier<int> counter = ValueNotifier<int>(0);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.value++,
        child: const Icon(Icons.add, color: Colors.white),
      ),
      body: Center(
        child: ValueListenableBuilder(
          valueListenable: counter,
          builder: (context, value, child) {
            return Text(value.toString());
          },
        ),
      ),
    );
  }
}

这种方法,即使代码稍长一点,也比传统的 setState 更有效。唯一需要重新绘制的部分是 Text widget 。对于此示例,无需重新构建整个 widget、Scaffold、AppBar,甚至是触发更改的 FloatingActionButton。

可以轻松扩展 ValueNotifier 类并使用自定义函数制作自定义 ValueNotifier。值数据类型应在扩展时指定。该类本身将包含一个属性值,该属性值指定您的自定义 ValueNotifier 类的当前值。

class CounterNotifier extends ValueNotifier<int> {
  CounterNotifier({int? value}) : super(value ?? 0);

  void increment() {
    value++;
  }

  void decrement() {
    value--;
  }
}

带有自定义 ValueNotifier 的完整计数器代码:

import 'package:flutter/material.dart';

class CounterNotifier extends ValueNotifier<int> {
  CounterNotifier({int? value}) : super(value ?? 0);

  void increment() {
    value++;
  }

  void decrement() {
    value--;
  }
}

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

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

class _MyAppState extends State<MyApp> {
  CounterNotifier counter = CounterNotifier();
  @override
  void dispose() {
    counter.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Row(
        children: [
          FloatingActionButton(
            onPressed: () => counter.increment(),
            child: const Icon(Icons.add, color: Colors.white),
          ),
          FloatingActionButton(
            onPressed: () => counter.increment(),
            child: const Icon(Icons.add, color: Colors.white),
          ),
        ],
      ),
      body: Center(
        child: ValueListenableBuilder(
          valueListenable: counter,
          builder: (context, value, child) {
            return Text(value.toString());
          },
        ),
      ),
    );
  }
}

自定义 ValueNotifier。

让我们先看看为什么我们需要自定义 ValueNotifier:

  • 用相关函数封装数据。
  • 数据相关逻辑和 UI 逻辑之间的抽象。
  • 提供自定义函数来修改和转换数据。

默认情况下,我们可以像这样直接与通知器的值交互:

Modify an <int> ValueNotifier:
- valueNotifier.value = 10;
- valueNotifier.value++;
- valueNotifier.value--;

我们能够与 ValueNotifier 值进行交互。但是,我们必须在前端一次又一次地声明所有这些语句。如果我们想将所有这些功能声明为函数并将它们绑定到 ValueNotifier 类,那么我们需要创建我们的自定义 ValueNotifier。我们像这样扩展 ValueNotifier 类:

image.png

下面你可以看到一个如何在 Flutter 中创建自定义 ValueNotifier 类的实例:

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

class MyNotifier extends ValueNotifier<int> {
  MyNotifier(int value) : super(value);

  void increment() {
    value++;
  }

  void decrement() {
    value--;
  }

  void performSomeCalcuations() {
    value = pow(value, 2).toInt();
  }

  void reset() {
    value = -1;
  }
}

当我们将 ValueNotifier 扩展到任何类时,我们可以使用函数轻松修改我们的数据。在类中,我们可以使用 value 关键字访问 ValueNotifier 的当前值。您可能会注意到 value 没有在任何地方声明,因为它是由 ValueNotifier 类本身提供的。

我们可以像使用任何 ValueNotifier 一样简单地使用这个类。我们使用 ValueListenableBuilder 来使用我们的自定义 ValueNotifier。

在 Flutter 中使用自定义 ValueNotifier

我们可以使用 AnimatedBuilder 或 ValueListenableBuilder 使用我们的 ValueNotifier。现在让我们看看如何在 Flutter 中使用 ValueListenableBuilder 来消费我们的自定义 ValueNotifier:

import "package:flutter/material.dart";

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

  @override
  State<CustomValueNotifierExample> createState() =>
      _CustomValueNotifierExampleState();
}

// 1 
final MyNotifier myNotifier = MyNotifier(0);

class _CustomValueNotifierExampleState
    extends State<CustomValueNotifierExample> {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder( // 2 
      valueListenable: myNotifier,
      builder: (context, value, child) {
        return Scaffold(
          floatingActionButton: Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              FloatingActionButton(
                onPressed: () => myNotifier.performSomeCalcuations(),// 3 
                child: const Icon(Icons.arrow_circle_up),
              ),
              const SizedBox(width: 10),
              FloatingActionButton(
                onPressed: () => myNotifier.increment(),// 4 
                child: const Icon(Icons.plus_one),
              ),
              const SizedBox(width: 10),
              FloatingActionButton(
                onPressed: () => myNotifier.reset(),// 5 
                child: const Icon(Icons.restore_page),
              ),
            ],
          ),
          body: Center(
            child: Text(value.toString()), // 6 
          ),
        );
      },
    );
  }
}

分步说明:

  • 首先,我们创建自定义 ValueNotifier 的单例实例。
  • 在这里,我们使用 ValueListenableBuilder 使用我们的通知器。
  • 在步骤 3、4、5 和 6 中,我们使用自定义 ValueNotifier 中描述的方法。

如果我们要消费ValueNotifier,有两种方式:

  • 如果需要跨两个或多个页面的 ValueNotifier,则使用 Singleton 实例。在上面的示例中,我们使用的是单例实例。
  • 仅在需要时创建实例,仅限于 Widget 范围。

处理 ValueNotifier

在我们使用完自定义 ValueNotifier 对象后,必须处理 ValueNotifier。处理掉 ValueNotifier 后,它就不能再使用了。因此,请确保您不需要任何其他页面上的 ValueNotifier。

@override
void dispose() {
  myNotifier.dispose();
  super.dispose();
}

结论:

Flutter 有很多状态管理技术。无论是使用 GetX、BLoC、RiverPod、Provider 等高级软件包,还是仅使用 setState。 ValueNotifier 是 Flutter 中的原生功能,ValueListenableBuilder 提供了对特定段的优化重建。如果想通知应用程序的某些部分有关值更改,请记住解决方案 ValueNotifer。

由于 Flutter 可以快速渲染一棵 widget 树,因此两种解决方案都是正确的。然而,作为一名开发人员,我相信优化和干净的代码。各种建设者帮助我们做到这一点。 (例如:StreamBuilder、FutureBuilder、ValueListenableBuilder 等)。