Flutter数据观察与响应式编程插件dart_observable的使用
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; } }, );
集合
目前支持list
、set
和map
。当集合被修改时,可观察对象会发出变更。这些变更在操作符中用于仅转换已更改的值。
示例: 向包含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
更多关于Flutter数据观察与响应式编程插件dart_observable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用dart_observable
插件进行数据观察与响应式编程的代码示例。请注意,dart_observable
并不是Flutter或Dart官方推荐的标准库,而是一个第三方库,用于实现响应式编程模式。在实际开发中,Flutter社区更推荐使用provider
、riverpod
或get_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。
注意:
- 由于
dart_observable
库可能不是最新的或者不是广泛使用的,因此在实际项目中,更推荐使用如provider
、riverpod
等成熟的状态管理库。 - 在
dispose
方法中移除监听器是一个良好的实践,可以避免内存泄漏。
希望这个示例对你有帮助!如果你有其他问题或需要进一步的帮助,请随时提问。