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

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

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

概述

RxCommand 是一个基于 Reactive Extensions (Rx) 的事件处理抽象库。它借鉴了 ReactiveUI 框架中的 ReactiveCommand,并大量使用了 RxDart 包。RxCommand 可以将给定的处理函数封装起来,并通过其 Stream 接口发布方法的结果。此外,它还提供了当前执行状态、命令是否可以执行以及命令执行过程中可能抛出的异常等信息。

安装

pubspec.yaml 文件中添加以下依赖:

dependencies:
  rxdart: ^0.27.3
  rx_command: ^5.0.0

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

基本用法

创建 RxCommand

RxCommand 是一个泛型类 RxCommand<TParam, TResult>,其中 TParam 是调用 execute 方法时传递的数据类型,TResult 是处理函数的返回类型。如果处理函数不接受参数或不返回值,可以使用 void 类型。

同步命令

import 'package:rx_command/rx_command.dart';

final command = RxCommand.createSync<int, String>((myInt) => "$myInt");

command.listen((s) => print(s)); // 设置监听器,等待事件

command.execute(10); // 监听器将打印 "10"

异步命令

final asyncCommand = RxCommand.createAsync<int, String>((myInt) async {
  await Future.delayed(Duration(seconds: 1));
  return "Async $myInt";
});

asyncCommand.listen((s) => print(s)); // 设置监听器,等待事件

asyncCommand.execute(10); // 1秒后监听器将打印 "Async 10"

使用限制流

你可以通过 restriction 参数来控制命令是否可以执行。例如,当某个开关的状态改变时,可以启用或禁用命令:

final switchChangedCommand = RxCommand.createSync<bool, bool>((b) => b);

final updateWeatherCommand = RxCommand.createAsync<String, List<WeatherEntry>>(update, switchChangedCommand);

错误处理

默认情况下,所有由封装函数抛出的异常都会被捕获并忽略。如果你希望对这些异常作出反应,可以监听 thrownException 属性。如果你想强制 RxCommand 不捕获异常,可以设置 throwExceptions 属性为 true

final command = RxCommand.createSync<int, String>((myInt) {
  if (myInt < 0) throw Exception("Negative number");
  return "$myInt";
});

command.thrownException.listen((ex) => print("Error: $ex"));

command.execute(-1); // 将打印 "Error: Negative number"

在 Flutter 应用中使用 RxCommand

ViewModel

通常,RxCommand 用于页面的 ViewModel 中。ViewModel 可以通过 InheritedWidgetGetIt 等方式提供给 Widgets。

import 'package:rxdart/rxdart.dart';
import 'package:rx_command/rx_command.dart';

class WeatherViewModel {
  final RxCommand<String, List<WeatherEntry>> updateWeatherCommand;
  final RxCommand<bool, bool> switchChangedCommand;

  WeatherViewModel() {
    switchChangedCommand = RxCommand.createSync<bool, bool>((b) => b);

    updateWeatherCommand = RxCommand.createAsync<String, List<WeatherEntry>>(update, switchChangedCommand);
  }

  Future<List<WeatherEntry>> update(String city) async {
    // 模拟网络请求
    await Future.delayed(Duration(seconds: 1));
    return [WeatherEntry(city, 10.0, 30.0, "sunny", 12)];
  }
}

View

在 View 中,可以通过 StreamBuilder 来监听 RxCommand 的结果,并根据不同的状态显示不同的 UI 元素。

import 'package:flutter/material.dart';
import 'package:rx_command/rx_command.dart';
import 'weather_viewmodel.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final viewModel = TheViewModel.of(context);

    return Scaffold(
      appBar: AppBar(title: Text('Weather App')),
      body: Column(
        children: [
          TextField(
            onChanged: viewModel.textChangedCommand,
          ),
          StreamBuilder<bool>(
            stream: viewModel.updateWeatherCommand.isExecuting,
            builder: (context, snapshot) {
              if (snapshot.hasData && snapshot.data!) {
                return CircularProgressIndicator();
              }
              return WeatherListView(viewModel.updateWeatherCommand);
            },
          ),
          Row(
            children: [
              ElevatedButton(
                onPressed: viewModel.updateWeatherCommand.canExecute ? () => viewModel.updateWeatherCommand.execute("London") : null,
                child: Text('Update Weather'),
              ),
              Switch(
                value: viewModel.switchChangedCommand.lastResult ?? false,
                onChanged: (value) => viewModel.switchChangedCommand.execute(value),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class WeatherListView extends StatelessWidget {
  final RxCommand<String, List<WeatherEntry>> command;

  WeatherListView(this.command);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<WeatherEntry>>(
      stream: command,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return ListView.builder(
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              final weather = snapshot.data![index];
              return ListTile(
                title: Text(weather.city),
                subtitle: Text("${weather.temperature}°C"),
              );
            },
          );
        }
        return Center(child: Text('No data'));
      },
    );
  }
}

InheritedWidget

使用 InheritedWidget 将 ViewModel 提供给子 Widgets。

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

class TheViewModel extends InheritedWidget {
  final WeatherViewModel theModel;

  const TheViewModel({Key? key, required this.theModel, required Widget child})
      : super(key: key, child: child);

  static WeatherViewModel of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<TheViewModel>()!.theModel;

  @override
  bool updateShouldNotify(TheViewModel oldWidget) => theModel != oldWidget.theModel;
}

主程序入口

import 'package:flutter/material.dart';
import 'homepage.dart';
import 'weather_viewmodel.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() {
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  late WeatherViewModel viewModelData;

  @override
  void initState() {
    viewModelData = WeatherViewModel();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return TheViewModel(
      theModel: viewModelData,
      child: MaterialApp(title: 'Flutter Demo', home: HomePage()),
    );
  }
}

总结

RxCommand 是一个强大的工具,可以帮助你在 Flutter 应用中实现响应式编程。通过将业务逻辑封装在 ViewModel 中,并使用 StreamBuilderInheritedWidget,你可以轻松地管理应用的状态和数据流。希望本文档能帮助你更好地理解和使用 RxCommand


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

1 回复

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


在Flutter中,rx_command 是一个用于响应式编程的插件,它主要用于处理命令(即用户交互事件)和它们的执行状态。这在处理按钮点击、表单提交等用户交互时非常有用,因为它允许你将命令的执行状态(如加载中、成功、失败)绑定到UI上。

以下是一个简单的例子,展示如何在Flutter中使用 rx_command

首先,确保你在 pubspec.yaml 文件中添加了 rx_command 依赖:

dependencies:
  flutter:
    sdk: flutter
  rx_command: ^x.y.z  # 请替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,在你的 Dart 文件中使用 rx_command。以下是一个完整的示例:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  // 定义一个简单的命令,它接收一个字符串参数并返回该字符串的长度
  final RxCommand<String, int> lengthCommand = RxCommand.create<String, int>(
    (String input) async {
      // 模拟一个异步操作,例如网络请求或数据处理
      await Future.delayed(Duration(seconds: 1));
      return input?.length ?? 0;
    },
    // 定义一个初始状态,这里我们使用BehaviorSubject来持有状态
    initialState: RxState.idle(),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('RxCommand Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextField(
              decoration: InputDecoration(labelText: 'Enter text'),
              onChanged: (value) {
                // 当文本变化时,更新命令的输入参数
                lengthCommand.execute(value);
              },
            ),
            SizedBox(height: 20),
            RxBuilder<RxState<int>>(
              command: lengthCommand,
              builder: (context, state) {
                if (state.isExecuting) {
                  return CircularProgressIndicator();
                } else if (state.hasError) {
                  return Text('Error: ${state.error}');
                } else if (state.hasResult) {
                  return Text('Length: ${state.result}');
                } else {
                  return Text('No input');
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

在这个例子中:

  1. 我们定义了一个 RxCommand<String, int> 类型的命令 lengthCommand,它接收一个字符串并返回该字符串的长度。
  2. TextFieldonChanged 回调中,我们调用 lengthCommand.execute(value) 来执行命令,并将用户输入的文本作为参数传递。
  3. 我们使用 RxBuilder 来监听命令的状态。根据命令的状态(正在执行、有错误、有结果、无输入),我们显示不同的UI元素(如进度指示器、错误消息、结果文本或无输入提示)。

这种方式使得处理用户交互和更新UI变得非常简洁和响应式。

回到顶部