Flutter观察者模式插件yet_another_observer的使用

Flutter观察者模式插件yet_another_observer的使用

该插件允许您观察任何变量或表达式的更改,而无需使用任何特殊的类型或注解。这使得它非常适合在现有的项目中使用,尤其是在需要观察某些内容但又不想改变现有变量类型的场景下。

如果您曾经发现自己添加了类似_lastValueif(value != _lastValue)这样的代码,那么这个插件就是为您设计的,并以更优雅的方式处理这些细节,避免在代码中充斥过多的变量和条件判断语句。

特性

此观察者依赖于手动调用更新方法来检查被观察变量或表达式的变化。这使其非常适合在Flutter小部件中使用,在那里观察者可以在每次构建时进行更新。

此外,还有一个管理器类,可以用来简化在一个地方管理和更新多个观察者。

为了进一步简化使用,该插件还包括一个观察者小部件,可以用来包装其他小部件并自动触发更新,以及一个混入类,用于简化从StatefulWidget中管理和更新多个观察者。

基本用法

以下是一个简单的例子,展示了如何使用yet_another_observer插件:

int value = 1;

// 创建一个观察者实例
YAObserver observer = YAObserver(
  () => value, // 提供一个返回值的函数
  onChanged: (event) {
    print('value=${event.value}'); // 当值发生变化时执行回调
  },
  updateImmediately: true // 立即触发一次更新
);

value = 2; // 修改值
observer.update(); // 手动触发更新
// 输出: value=2

在这个例子中,我们简单地检查通过调用() => value得到的结果,与上一次的值进行比较,并在值发生变化时触发onChanged回调。

事件

每当检测到被观察值的变化时,观察者的onChanged方法将被调用,并传递一个事件参数。该事件参数包含了有关变化的信息:

  • value 是当前被观察值的当前值。
  • changeTime 是检测到变化的时间(不一定是变量实际更改的时间,而是观察者调用update方法的时间)。
  • history 包含一个历史事件列表。最大历史事件数量在创建观察者时定义,最新的历史事件位于索引0,前一个事件位于索引1,依此类推。

观察非标量值

1. 获取值

第一个可能出现的问题是,如果您提供一个对象作为要观察的值,那么同一个对象实例可能会被自身比较!这是因为观察者存储了之前的值,并在下一次更新时将其与当前值进行比较。

解决方法是始终为值函数提供一个唯一的实例。例如,如果您想观察一个List,而不是写成:

YAObserver(
  () => myList,
  onChanged: (event){
    // 可能永远不会被调用,因为旧值和当前值可能指向同一个列表,因此看起来(对观察者来说)从未改变。
  }
)

您可以写成:

YAObserver(
  () => myList.toList(),
  onChanged: (event){
  }
)

上面的例子使用toList()来创建列表的一个唯一实例,并将其存储为旧值。下次调用update()时,旧值将与当前值不同。这带来了第二个问题…

2. 比较先前和当前值

当观察非标量值(如List)时,需要特别注意。观察者会默认使用==运算符来比较先前和当前的被观察值。

考虑以下代码:

List<String> myList = ['abc'];

YAObserver observer = YAObserver<List<String>>(
  () => myList.toList(),
  onChanged: (event){
    print('The list changed from ${event.history[0].value} to ${event.value}.');
  },
  maxHistoryLength: 1,
  updateImmediately: true,
  fireOnFirstUpdate: false
);

myList = ['123'];
observer.update();

myList = ['123'];
observer.update();

myList.add('foobar');
observer.update();

这将输出:

The list changed from [abc] to [123].
The list changed from [123] to [123].

您可能期望的输出是:

The list changed from [abc] to [123].
The list changed from [123] to [123, foobar].

问题在于我们在比较List实例而不是它们的实际内容。这个问题可以通过向观察者提供自定义的hasChanged函数来解决。为了实现预期结果,有几种选择。

首先,您可以将List转换为标量值,例如通过JSON编码它或简单地提供myList.toString()的值给观察者。然后您将比较字符串,这将起作用。然而,这种方法的问题是两个具有相同内容但顺序不同的列表将生成两个不同的字符串,因此它们不会相等。

更好的选择是使用Dart集合库,它提供了一些方便的函数来比较列表和其他集合。

import 'package:collection/collection.dart';

YAObserver observer = YAObserver<List<String>>(
  () => myList,
  onChanged: (event){
    print('The list changed from ${event.history[0].value} to ${event.value}.');
  },
  hasChanged: (old, current) => ! const ListEquality().equals(old, current),
  maxHistoryLength: 1,
  updateImmediately: true,
  fireOnFirstUpdate: false
);

这将输出:

The list changed from [abc] to [123].
The list changed from [123] to [123, foobar].

额外信息

查看示例以获取更多关于该插件功能的信息。也可以参阅代码中的内联文档。


完整示例代码

以下是完整的示例代码,展示了如何使用yet_another_observer插件:

import 'dart:async';

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

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

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Yet Another Observer example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Yet Another Observer example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with YAObserverStatefulMixin {
  int _animationStep = 0;
  final List<String> _animationSequence = ['.', '..', '...', ' ...', '  ...', '   ..', '    .', '     '];

  int _counter = 0;

  late final YAObserver<int> _counterObserver;

  [@override](/user/override)
  void initState() {
    super.initState();

    _counterObserver = observe<int>(
      () => _counter,
      maxHistoryLength: 1,
      updateImmediately: true,
      fireOnFirstUpdate: false,
      onChanged: (event) {
        if (event.value == 3) {
          scheduleMicrotask(() => showDialog(
            context: context,
            builder: (ctx) => AlertDialog(
              title: Text('${event.history[0].value} ---\> ${event.value}'),
              content: const Text(
                'This dialog was shown from an observer that '
                'observes the number on every rebuild. When the observed number '
                'is 3 but was previously something else (i.e. it changed), the '
                'observer function was triggered.\n\n'
                'If you had simply shown the dialog from the build function every '
                'time the number is 3, you would have ended up adding a new dialog '
                'on each rebuild due to the build function being triggered by other '
                'things than just the +/- buttons (i.e. the animation).'
              ),
              actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('OK'))],
            )
          ));
        }
      },
    );

    Timer.periodic(const Duration(milliseconds: 100), (timer) {
      if (mounted) {
        setState(() {
          _animationStep++;
          if (_animationStep >= _animationSequence.length) _animationStep = 0;
        });
      }
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    super.build(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(_animationSequence[_animationStep], textScaleFactor: 5),
              const SizedBox(height: 40),
              const Text('3 is the magic number...'),
              Text('$_counter', textScaleFactor: 3),
            ],
          ),
        ),
      ),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(20),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              onPressed: () => setState(() => _counter--),
              child: const Icon(Icons.remove),
            ),
            const SizedBox(width: 20),
            FloatingActionButton(
              onPressed: () => setState(() => _counter++),
              child: const Icon(Icons.add),
            ),
          ],
        ),
      ),
    );
  }
}
1 回复

更多关于Flutter观察者模式插件yet_another_observer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


yet_another_observer 是一个用于 Flutter 的观察者模式插件,它允许你在应用程序中实现观察者模式,以便在数据发生变化时通知观察者。这个插件非常轻量级且易于使用,适合在 Flutter 项目中使用。

安装

首先,你需要在 pubspec.yaml 文件中添加依赖:

dependencies:
  yet_another_observer: ^0.1.0

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

基本用法

1. 创建观察者

首先,你需要创建一个观察者类,该类将继承自 Observer

import 'package:yet_another_observer/yet_another_observer.dart';

class MyObserver extends Observer {
  @override
  void update(Observable observable, dynamic data) {
    // 当被观察对象发生变化时,会调用此方法
    print('Observer notified with data: $data');
  }
}

2. 创建被观察对象

接下来,创建一个被观察对象类,该类将继承自 Observable

class MyObservable extends Observable {
  void doSomething() {
    // 这里可以执行一些操作
    // 然后通知所有观察者
    notifyObservers('Something happened!');
  }
}

3. 使用观察者和被观察对象

现在你可以在 Flutter 应用中使用观察者和被观察对象了:

void main() {
  final observable = MyObservable();
  final observer = MyObserver();

  // 添加观察者
  observable.addObserver(observer);

  // 触发被观察对象的操作
  observable.doSomething();

  // 输出: Observer notified with data: Something happened!

  // 移除观察者
  observable.removeObserver(observer);
}

高级用法

1. 传递复杂数据

你可以传递任何类型的数据给观察者。例如,传递一个自定义对象:

class MyData {
  final String message;
  MyData(this.message);
}

class MyObservable extends Observable {
  void doSomething() {
    notifyObservers(MyData('Something happened!'));
  }
}

class MyObserver extends Observer {
  @override
  void update(Observable observable, dynamic data) {
    if (data is MyData) {
      print('Observer notified with message: ${data.message}');
    }
  }
}

2. 多个观察者

你可以添加多个观察者,它们都会在被观察对象发生变化时收到通知:

void main() {
  final observable = MyObservable();
  final observer1 = MyObserver();
  final observer2 = MyObserver();

  observable.addObserver(observer1);
  observable.addObserver(observer2);

  observable.doSomething();

  // 输出:
  // Observer notified with message: Something happened!
  // Observer notified with message: Something happened!
}
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!