Flutter响应式编程插件rx_observable的使用

Flutter响应式编程插件rx_observable的使用

Simple reactive variables and widgets. 类似于Livedata/observable/mobx,但无需代码生成和复杂的样板代码。

适用于MVVM等模式。

特性

创建反应变量,监听它们并更新UI。

使用方法

定义变量的方法如下:

var text = "Hello".obs;

使用Observer小部件来订阅更改:

return Scaffold(
  appBar: AppBar(),
  body: Column(
    children: [
      /// 直接使用Observer小部件更新UI值
      Observer(text, (v) => Text(v)),
      /// 使用扩展方法创建相同的Observer小部件
      text.observer((v) => Text(v)),
      /// 使用大构建版本
      Observer.builder(
        observable: text,
        builder: (context, v) {
          return Text(v);
        }
      )
    ],
  ),
);

更新值的方法如下:

text.value = "GoodBye";

监听的方法如下:

text.listen((v) {
  print("New value is $v");
});

关闭的方法如下:

text.close();

其他信息

更多示例:

// 在Observer中监听两个或三个observables
Observer3.builder(
  observable: text,
  observable2: text2,
  observable3: text3,
  builder: (context, v1, v2, v3) {
    return Text("$v1 $v2 $v3");
  }
),

// 所有构造函数结果相同
var test1 = Observable(25);
var test2 = Observable<int>(25);
var test3 = Obs(25);
var test4 = 25.obs;
var test5 = ObservableInt(25);
var test6 = ObservableReadOnly(25); /// 只能读取此值

test1.listen((v) {
  if (kDebugMode) { print(v); }
});

/// 不更新UI的情况下监听observable
/// 也可以查看ObservableConsumer小部件以获取两者
var widgetListener = ObservableListener(
  observable: test1,
  listener: (v, context) {
    if (kDebugMode) { print(v); }
  },
  child: const SizedBox(),
);

还可以检查RxSubsMixin,以便轻松处理您的反应变量、订阅和流的关闭(例如在viewModel/bloc/store/repository中)。

与状态概念结合使用

您可以将这种方法与状态概念结合使用,即使_bloc创作者告诉你状态必须是不可变的等等。

只需使用这个简单技巧,并确保始终关闭您的反应变量(使用RxSubsMixin帮助),就不会有问题发生:

/// 创建一些viewModel/Bloc样式的类,带自动清理的mixin和可清理接口
class ViewModelExample with RxSubsMixin implements IDisposable {

  /// 定义用于监听状态的反应变量。就像Bloc模式一样。
  Observable<T> state = LoadingState();

  /// 简单示例变量。你可以将状态概念与简单变量混合使用或不使用,这取决于你。
  var title = "Hello".obs;
  
  /// 这里是魔法。为某个列表定义一个私有变量。它将在状态中使用。
  final _contactsList = Observable<List<Contact>>([]);

  ViewModelExample() {
    /// 自动清理这些变量
    regs([state, title, _contactsList]);
  }
  
  void loadContacts() {
    /// ...做一些魔法从任何地方获取异步数据
    /// 然后将它的值作为指针传递给状态,因为它不是简单类型。
    state.value = LoadedState(contacts: _contactsList);
  }
}

/// 状态本身。哇!你有一个带有反应变量的状态,但它不会导致任何泄漏(可能?)
/// 因为实际值在ViewModelExample中,而状态中只有指向它的链接。
/// 当然,由于并发访问列表可能会出现一些问题,但在我的实践中从未发生过。
class LoadedState extends BaseState {
  final ObservableReadOnly<List<Contact>> contacts;

  LoadedState({required this.contacts});
}

基本UI示例:

var vm = context.read<ViewModelExample>();
Observer(vm.state, (vmState) {
  switch (vmState) {
    case LoadingState():
      return const LoadingWidget();
    case LoadedState():
      return Observer(vmState.contacts, (vmContacts) {
        if (vmContacts.isEmpty) {
          return EmptyWidget();
        } else {
          return ListWidget(vmContacts);
        }
      });
    case ErrorState():
      return ErrorWidget();
  }
})

为什么比mobX、Bloc、GetX更好?

MobX的弱点在于代码生成。在某些情况下,这可能导致开发过程中出现问题,例如处理最终的延迟反应变量可能很困难。

Bloc有太多的样板代码,管理和整个状态涉及太多工作。它需要刷新整个状态才能改变一个值。

另一方面,GetX(或Get)包含太多功能,可能存在bug,核心复杂。

示例代码

以下是完整的示例代码:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:rx_observable/rx_observable.dart';
import 'package:rx_observable/widgets.dart';

import 'example_screen.dart';

void main() {
  // 所有构造函数结果相同
  var test1 = Observable(25);
  var test2 = Observable<int>(25);
  var test3 = Obs(25);
  var test4 = 25.obs;
  var test5 = ObservableInt(25);
  var test6 = ObservableReadOnly(25); /// 只能读取此值

  test1.close();
  test2.close();
  test3.close();
  test4.close();
  test5.close();
  test6.close();

  runApp(const ExampleApp());
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  [@override](/user/override)
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  var test1 = Observable(25);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rx Observable Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      /// 不更新UI的情况下监听observable
      /// 也可以查看ObservableConsumer小部件以获取两者
      home: ObservableListener(
        observable: test1,
        listener: (v, context) {
          if (kDebugMode) { print(v); }
        },
        /// 查看其他示例在[ExampleScreen]
        child: const ExampleScreen()
      ),
    );
  }
}

更多关于Flutter响应式编程插件rx_observable的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

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


rx_observable 并不是 Flutter 官方或社区中广泛使用的响应式编程插件。在 Flutter 中,最常用的响应式编程库是 RxDart,它是基于 Dart 语言的响应式扩展(Reactive Extensions)实现。RxDart 提供了丰富的操作符和工具,用于处理异步数据流和事件流。

如果你正在寻找一个响应式编程的解决方案,建议使用 RxDart。以下是关于如何在 Flutter 中使用 RxDart 的基本指南:


1. 添加 RxDart 依赖

pubspec.yaml 文件中添加 RxDart 依赖:

dependencies:
  flutter:
    sdk: flutter
  rxdart: ^0.27.7 # 请使用最新版本

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


2. 使用 RxDart 的基本概念

RxDart 的核心是 Observable(可观察对象),它代表一个异步数据流。你可以使用 RxDart 的操作符来转换、过滤和组合这些数据流。

以下是一个简单的示例:

import 'package:rxdart/rxdart.dart';

void main() {
  // 创建一个 Observable
  final observable = Observable.fromIterable([1, 2, 3, 4, 5]);

  // 使用操作符转换数据流
  observable
      .map((value) => value * 2) // 将每个值乘以 2
      .where((value) => value > 5) // 过滤掉小于等于 5 的值
      .listen((value) {
    print(value); // 输出: 6, 8, 10
  });
}

3. 在 Flutter 中使用 RxDart

RxDart 通常与 Flutter 的 StreamBuilderStatefulWidget 结合使用,以实现响应式 UI。

以下是一个简单的 Flutter 示例:

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

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

class _CounterPageState extends State<CounterPage> {
  final BehaviorSubject<int> _counterSubject = BehaviorSubject.seeded(0);

  void _incrementCounter() {
    _counterSubject.add(_counterSubject.value + 1);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('RxDart Counter')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _counterSubject.stream,
          builder: (context, snapshot) {
            return Text(
              'Counter: ${snapshot.data ?? 0}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }

  [@override](/user/override)
  void dispose() {
    _counterSubject.close(); // 关闭 Subject
    super.dispose();
  }
}

void main() => runApp(MaterialApp(
  home: CounterPage(),
));
回到顶部