Flutter数据观察与响应式编程插件dart_observable的使用

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

Flutter数据观察与响应式编程插件dart_observable的使用

关于

Observable是一个状态管理库,它支持基于观察者模式的响应式编程,而无需使用流。该库设计简单、灵活且高效,并具有显式的注册机制以避免隐藏依赖。

该库可以与任何架构一起使用。

该库特别关注集合操作,提供了跟踪集合(如集合、映射或列表)变化的方法。

动机

最初的灵感来自于Kotlin的StateFlow。该库旨在简单、灵活和高效,但通过显式注册来避免隐藏依赖。

特性

  • 可变和不可变版本

    • 该库提供了可变和不可变版本的可观察对象,包括集合。
    • 可变版本称为Rx,而不可变版本称为Observable
    • 您可以暴露不可变版本,同时在内部使用可变版本。
    • 不可变集合防止直接修改,确保更改通过可观察对象发出。
  • 变更跟踪

    • 支持跟踪集合(如集合、映射或列表)的变化。
    • 操作符和监听器可以利用变更仅转换已更改的值,从而显著提高性能。
  • 工厂和操作符

    • 该库包含工厂和操作符。

Observable

  • Observable是所有可观察对象的基本类。它是不可变版本的可观察对象。Rx是可变版本,实现了Observable接口。
  • 可以由多个监听器监听可观察对象。
// 定义一个可变整数类型的Rx对象
Rx<int> _rxNumber = Rx<int>(1);
// 提供一个不可变的getter方法
Observable<int> get rxNumber => _rxNumber; 
工厂方法
  • just 创建一个带有单个值的可观察对象。

    final Observable<int> rxInt = Observable<int>.just(0);
    
  • combineLatest 将多个可观察对象合并为一个可观察对象。每当其中一个可观察对象发生变化时,合并后的可观察对象将发出新值。

    final Rx<int> rxInt1 = Rx<int>(0);
    final Rx<int> rxInt2 = Rx<int>(0);
    final Rx<int> rxInt3 = Rx<int>(0);
    
    final Observable<int> rxInt = Observable<int>.combineLatest(
      observables: <Observable<int>>[rxInt1, rxInt2, rxInt3],
      combiner: () {
        return rxInt1.value + rxInt2.value + rxInt3.value;
      },
    );
    
  • fromFuture 从一个未来值创建一个可观察对象。

    final Future<int> future = Future<int>.value(10);
    final Observable<int> rxInt = Observable<int>.fromFuture(
        initial: 0,
        future: future,
    );
    
  • fromStream 从一个流创建一个可观察对象,监听流并更新可观察对象。

    final Stream<int> stream = Stream<int>.value(10);
    final Observable<int> rxInt = Observable<int>.fromStream(
        initial: 0,
        stream: stream,
    );
    
操作符
  • map 转换可观察对象的值。

    Observable<int> rxSource = Observable<int>.just(0);
    Observable<String> rxString = rxSource.map((value) => value.toString());
    
  • filter 过滤可观察对象的值。

    Observable<int> rxSource = Observable<int>.just(0);
    Observable<int> rxFiltered = rxSource.filter((value) => value % 2 == 0);
    
  • combineWith 将可观察对象与其他可观察对象结合。

    final Rx<int> observable1 = Rx<int>(1);
    final Rx<int> observable2 = Rx<int>(2);
    final Observable<int> combined = observable1.combineWith<int, int>(
      other: observable2,
      combiner: (final int value1, final int value2) => value1 + value2,
    );
    
  • handleError 处理可观察对象中的错误。提供一种从错误中恢复的方法。接受一个可选谓词来过滤错误。

    final Observable<int> rxInt = Observable<int>.just(0);
    final Observable<int> rxIntHandled = rxInt.handleError(
      (final dynamic error, final Emitter<int> emit) {
        emit(1);
      },
      predicate: (final dynamic error) {
        return error is ArgumentError;
      });
    
  • transform 最灵活的操作符,允许以任何方式转换可观察对象。发射器用于向可观察对象发射任何新值。其他所有操作符都构建在此操作符之上。

    final Rx<int> rx = Rx<int>(0);
    final Observable<double> transformed = rx.transform<double>(
      initialProvider: (final int value) {
        return value * 2.5;
      },
      onChanged: (
        final int value,
        final Emitter<double> emitter,
      ) {
        emitter(value * 2.5);
      },
    );
    
  • transformAs 用于将可观察对象转换为新的集合,如列表、集合或映射。

    final Rx<String> rxSource = Rx<String>('Hello World');
    final ObservableList<String> rxTransformed = rxSource.transformAs.list<String>(
      transform: (
        final ObservableList<String> state,
        final String value,
        final Emitter<List<String>> emitter,
      ) {
        emitter(<String>[for (final String char in value.split('')) char]);
      },
    );
    
  • switchMap 在每次更改时,切换到由映射函数提供的新可观察对象。取消之前的可观察对象,并监听新的可观察对象。

    final RxInt rxType1 = RxInt(1);
    final RxInt rxType2 = RxInt(2);
    final RxInt rxType3 = RxInt(3);
    final RxList<int> rxSource = RxList<int>(<int>[1, 2, 3]);
    final Observable<int> rxSwitched = rxSource.switchMap<int>(
      (final List<int> state) {
        final int mod = state.length % 3;
        if (mod == 0) {
          return rxType1;
        } else if (mod == 1) {
          return rxType2;
        } else {
          return rxType3;
        }
      },
    );
    
  • switchMapAs 在每次更改时,切换到由映射函数提供的新集合。取消之前的可观察对象,并监听新的可观察对象。

    final RxMap<int, String> rxType1 = RxMap<int, String>({1: '1'});
    final RxMap<int, String> rxType2 = RxMap<int, String>({2: '2'});
    final RxMap<int, String> rxType3 = RxMap<int, String>({3: '3'});
    final Rx<int> rxSource = Rx<int>(0);
    final ObservableMap<int, String> rxSwitched = rxSource.switchMapAs.map<int, String>(
      mapper: (final int value) {
        final int mod = value % 3;
        if (mod == 0) {
          return rxType1;
        } else if (mod == 1) {
          return rxType2;
        } else {
          return rxType3;
        }
      },
    );
    

集合

目前支持listsetmap。当集合被修改时,可观察对象会发出变更。这些变更在操作符中用于仅转换已更改的值。

示例: 向包含10,000个项目的列表中添加一个值只会发出添加的值。监听器可以使用变更来转换该值。

以下示例中,初始列表只会在可观察对象被监听时进行一次转换。之后,仅对变更进行转换,而不是整个列表。

RxList<int> rxLargeList = RxList<int>(List.generate(10000, (index) => index));
ObservableList<int> rxEvenItems = rxLargeList.filterItem((final int item) => item % 2 == 0);
ObservableList<int> rxEvenItemsMapped = rxEvenItems.mapItem((final int item) => item * 2);
rxLargeList.add(10000);
rxLargeList.removeAt(0);
操作符

您可以使用基本操作符,但它们总是创建一个Observable而不是集合。有特定于集合的操作符,它们将返回相同的集合类型。这些操作符优化为仅转换已更改的值。

  • mapItem 转换集合中的每个项目。操作符仅发出已更改的项目。此操作符支持集合、列表和映射及其有状态版本。

    final RxList<int> rxList = RxList<int>(<int>[1, 2, 3]);
    final ObservableList<int> rxMapped = rxList.mapItem<int>((final int item) => item * 2);
    
  • filterItem 过滤集合中的项目。操作符仅发出已更改的项目。此操作符支持集合、列表和映射及其有状态版本。

    final RxList<int> rxList = RxList<int>(<int>[1, 2, 3]);
    final ObservableList<int> rxEvenItems = rxList.filterItem((final int item) => item % 2 == 0);
    

有状态集合

有状态集合支持自定义状态,例如加载、错误或任何用户定义的状态。结合了集合和自定义状态,允许可观察对象在任何给定时间表示其中之一。您可以创建任何自定义状态。

这种方法消除了需要多个可观察对象来管理不同状态的需求。例如,当从服务器获取项目列表时,可观察对象可以在单一统一状态下表示加载、错误或数据状态。此状态可用于监听器或下游操作符以处理不同的状态。

ObservableStatefulList<String, LoadingOrError> rxItems = getItemsFromServer();
rxItems.listen(onChange: (state) {
  state.when(
    onData: (ObservableListState<String> items) => , // 项目加载完成,
    onCustom: (LoadingOrError state) => , // 自定义状态,
  );
});

示例代码

以下是一个完整的示例demo,展示了如何使用dart_observable库:

import 'package:dart_observable/dart_observable.dart';

Future<void> main() async {
  final Controller controller = Controller();

  // 监听计数器的变化
  final Disposable counterListener = controller.rxCounter.listen(
    onChange: (final int value) {
      print('计数器更改为 $value');
    },
  );

  // 增加计数器
  controller.increment();
  final int rxCounter = controller.rxCounter.value;
  assert(rxCounter == 1);

  // 监听列表的变化
  final Disposable listListener = controller.rxItems.listen(
    onChange: (final List<String> immutableList) {
      final ObservableListChange<String> change = controller.rxItems.change;

      print('列表大小: ${immutableList.length}');
      final Map<int, String> added = change.added;
      final Map<int, String> removed = change.removed;
      final Map<int, ObservableItemChange<String>> updated = change.updated;

      if (added.isNotEmpty) {
        for (final MapEntry<int, String> entry in added.entries) {
          print('添加到位置: ${entry.key} ${entry.value}');
        }
      }

      if (removed.isNotEmpty) {
        for (final MapEntry<int, String> entry in removed.entries) {
          print('从位置移除: ${entry.key} ${entry.value}');
        }
      }

      if (updated.isNotEmpty) {
        for (final MapEntry<int, ObservableItemChange<String>> entry in updated.entries) {
          print('更新位置: ${entry.key} 从 ${entry.value.oldValue} 到 ${entry.value.newValue}');
        }
      }
    },
  );

  /// 观察者是冷的,因此在订阅之前不会计算初始状态。
  final Disposable mapListener = controller.rxItemsUppercasedMap.listen();
  final Disposable itemListener = controller.rxItemsUppercasedByItem.listen();

  /// 初始状态从源映射到目标。
  assert(controller.rxItemsUppercasedMap.value.length == 3);
  assert(controller.rxItemsUppercasedByItem.length == 3);

  controller.addItem('item4');
  assert(controller.rxItemsUppercasedByItem.length == 4);
  assert(controller.rxItemsUppercasedByItem[3] == 'ITEM4');

  assert(controller.rxItemsUppercasedMap.value.length == 4);
  // [] is not implemented on Observable<List>
  assert(controller.rxItemsUppercasedMap.value[3] == 'ITEM4');

  controller.updateItem(0, 'newItem');

  assert(controller.rxItemsUppercasedMap.value.length == 4);
  assert(controller.rxItemsUppercasedByItem.length == 4);

  assert(controller.rxItemsUppercasedByItem[0] == 'NEWITEM');
  assert(controller.rxItemsUppercasedMap.value[0] == 'NEWITEM');

  controller.removeItemAt(0);

  assert(controller.rxItemsUppercasedMap.value.length == 3);
  assert(controller.rxItemsUppercasedByItem.length == 3);

  assert(controller.rxItemsUppercasedByItem[0] == 'ITEM2');
  assert(controller.rxItemsUppercasedMap.value[0] == 'ITEM2');

  await listListener.dispose();
  await counterListener.dispose();
  await mapListener.dispose();
  await itemListener.dispose();
}

class Controller {
  /// 基本可变状态,可以持有任何类型。
  final Rx<int> _rxCounter = Rx<int>(0);
  final RxList<String> _rxItems = RxList<String>(<String>['item1', 'item2', 'item3']);

  /// 可变状态的不可变版本。
  late final Observable<int> rxCounter = _rxCounter;

  /// 对于集合(列表、映射、集合),返回的值总是不可变的。
  late final ObservableList<String> rxItems = _rxItems;

  /// 集合可观察对象用于处理集合内的更改。
  /// 这些更改在集合操作符中用于计算差异。
  /// 基础 [Observable.map] 操作符不处理更改,但可以映射到任何类型。
  late final Observable<List<String>> rxItemsUppercasedMap = _rxItems.map(
    (final List<String> immutableView) {
      return immutableView.map((final String item) => item.toUpperCase()).toList();
    },
  );

  /// [ObservableList.mapItem] 利用集合内的更改来计算差异。
  /// 因此对于集合来说,它比 [Observable.map] 更高效,因为它只会计算差异。
  /// 订阅集合时,初始数据包含在第一次更改中。
  late final ObservableList<String> rxItemsUppercasedByItem =
      _rxItems.mapItem((final String item) => item.toUpperCase());

  void addItem(final String item) {
    _rxItems.add(item);
  }

  void increment() {
    _rxCounter.value++;
  }

  void removeItemAt(final int position) {
    _rxItems.removeAt(position);
  }

  void updateItem(final int position, final String newItem) {
    _rxItems[position] = newItem;
  }
}

更多关于Flutter数据观察与响应式编程插件dart_observable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据观察与响应式编程插件dart_observable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用dart_observable插件进行数据观察与响应式编程的代码示例。请注意,dart_observable并不是Flutter或Dart官方推荐的标准库,而是一个第三方库,用于实现响应式编程模式。在实际开发中,Flutter社区更推荐使用providerriverpodget_it等状态管理库。不过,为了满足你的要求,这里展示如何使用dart_observable

首先,确保你的pubspec.yaml文件中已经添加了dart_observable的依赖:

dependencies:
  flutter:
    sdk: flutter
  dart_observable: ^x.y.z  # 请替换为实际版本号

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

接下来,是一个简单的示例,展示如何使用dart_observable进行数据绑定和响应式更新。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Observable Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 创建一个可观察的变量
  final ObservableString _counter = ObservableString('0');

  void _increment() {
    int currentValue = int.tryParse(_counter.value) ?? 0;
    _counter.value = (currentValue + 1).toString();
  }

  @override
  Widget build(BuildContext context) {
    // 监听变量的变化
    _counter.addListener(() {
      setState(() {});
    });

    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Observable Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              _counter.value,
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    // 移除监听器以避免内存泄漏
    _counter.removeListener(() {});
    super.dispose();
  }
}

在这个示例中,我们使用了dart_observable库中的ObservableString类来创建一个可观察的字符串变量。每当这个变量的值发生变化时,我们都会通过addListener方法添加一个监听器来调用setState方法,从而更新UI。

注意:

  1. 由于dart_observable库可能不是最新的或者不是广泛使用的,因此在实际项目中,更推荐使用如providerriverpod等成熟的状态管理库。
  2. dispose方法中移除监听器是一个良好的实践,可以避免内存泄漏。

希望这个示例对你有帮助!如果你有其他问题或需要进一步的帮助,请随时提问。

回到顶部