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

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

flutter_rx 是一个类似于 Redux 的状态管理库,它受到了 NgRx、flutter_redux 和 redux 的启发。以下是其核心概念和使用方法。

核心概念

  • Actions 描述了独特的事件,通常从小部件或返回的 [Effect] 中触发,例如按钮点击或从服务器加载数据的意图。
  • Reducers 是纯函数,用于处理状态变化。它们接受当前状态和最新的动作来计算新的状态。
  • Selectors 是纯函数,用于选择、衍生和组合部分状态。
  • Store 用于访问状态,它是状态的观察者和动作的观察者。
  • Effects 用于处理所有副作用,例如从远程服务器加载数据。

图表

下图表示了在 flutter_rx 中应用程序状态的整体流程。

使用示例

以下是一个使用 flutter_rx 的计数器应用的完整示例。

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

/// 应用的状态。
///
/// 状态必须扩展 [StoreState],并应提供 [==] 和 [hashCode] 的重写。
///
/// 如果没有提供 [==] 重写,选择记忆化将无法工作,这会导致不必要的重建。
class AppState extends StoreState {
  const AppState({required this.counter, this.isLoading = false});
  final int counter;
  final bool isLoading;

  AppState copyWith({int? counter, bool? isLoading}) {
    return AppState(
      counter: counter ?? this.counter,
      isLoading: isLoading ?? this.isLoading,
    );
  }

  [@override](/user/override)
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other.runtimeType != runtimeType) return false;
    return other is AppState &&
        other.counter == counter &&
        other.isLoading == isLoading;
  }

  [@override](/user/override)
  int get hashCode => counter.hashCode ^ isLoading.hashCode;
}

/// 动作描述了独特的事件,并通常从小部件或返回的 [Effect] 中触发。
///
/// 动作必须扩展 [StoreAction]。
class IncrementAction extends StoreAction {
  const IncrementAction();
}

/// 动作可以可选地包含状态。
class IncrementByAction extends StoreAction {
  const IncrementByAction({required this.value});
  final int value;
}

/// 动作可用于发起异步任务,例如从服务器获取数据。
class FetchCounterValueAction extends StoreAction {
  const FetchCounterValueAction();
}

class FetchCounterValueSuccessAction extends StoreAction {
  const FetchCounterValueSuccessAction({required this.value});
  final int value;
}

/// 减少器就是一个纯函数,它接受状态和动作,并返回新的状态。
///
/// 下面的减少器旨在针对特定的动作进行减少,例如 [IncrementAction],但减少器也可以针对任何通用的 [StoreAction] 进行减少。
AppState incrementCounterReducer(
  AppState state,
  IncrementAction action,
) {
  return state.copyWith(counter: state.counter + 1);
}

AppState incrementCounterByReducer(
  AppState state,
  IncrementByAction action,
) {
  return state.copyWith(counter: state.counter + action.value);
}

AppState fetchCounterValueReducer(
  AppState state,
  FetchCounterValueAction action,
) {
  return state.copyWith(
    isLoading: true,
  );
}

AppState fetchCounterValueSuccessReducer(
  AppState state,
  FetchCounterValueSuccessAction action,
) {
  return state.copyWith(
    counter: action.value,
    isLoading: false,
  );
}

/// [createReducer] 接受多个单一目的减少器并将它们组合在一起。
///
/// [On] 用于将特定的 [StoreAction] 映射到应该在该动作被分派时使用的减少器。
///
/// 例如,当接收到 [IncrementAction] 时,[incrementCounterReducer] 将被用来生成新的状态。
final reducer = createReducer<AppState>([
  On<AppState, IncrementAction>(incrementCounterReducer),
  On<AppState, IncrementByAction>(incrementCounterByReducer),
  On<AppState, FetchCounterValueAction>(fetchCounterValueReducer),
  On<AppState, FetchCounterValueSuccessAction>(fetchCounterValueSuccessReducer),
]);

/// 效应处理所有副作用,例如从远程服务器加载数据。
///
/// 效应接收来自存储的行动流。通常这个流会过滤特定的动作。
///
/// 行动只有在应用程序的减少器处理了这个动作并返回了一个新的应用程序状态之后才会被添加到效应接收的流中。这保证了存储的状态包括了由效应处理的动作的所有变更。
///
/// 效应可以选择返回一个或多个动作。这些动作将被分派。如果效应返回一个动作列表,它们将以列表的顺序被分派。
///
/// 效应不应该调用 dispatch 来分派新动作。
Effect<AppState> onFetchCounter = (
  Stream<StoreAction> actions,
  Store<AppState> store,
) {
  /// RxDart 可以在效应中使用,但不一定要使用。
  return actions.whereType<FetchCounterValueAction>().flatMap((action) {
    return Stream.fromFuture(fetchCounter()).map((value) {
      return FetchCounterValueSuccessAction(value: value);
    });
  });
};

/// 模拟数据调用。
///
/// 在实际应用中,这将是一个网络调用。
Future<int> fetchCounter() {
  return Future.delayed(const Duration(milliseconds: 100)).then((value) => 10);
}

void main() {
  /// 在主函数或状态对象内部创建您的存储作为最终变量。
  final store = Store<AppState>(
    /// 存储的初始状态。
    initialState: const AppState(counter: 0),

    /// 主减少器。
    ///
    /// 这可以是一个简单的纯函数,或者由多个较小的减少器组成。
    reducer: reducer,

    /// 要注册的效果列表。
    effects: [onFetchCounter],
  );

  runApp(
    /// StoreProvider 通常应该是顶级小部件。只有后代才能访问 StoreProvider 的状态。
    StoreProvider<AppState>(
      store: store,
      child: const MaterialApp(
        home: HomePage(),
      ),
    ),
  );
}

class HomePage extends StatelessWidget {
  const HomePage({
    super.key,
  });

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterRx 计数器演示'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '你已经按下了按钮这么多次:',
            ),

            /// StoreConnector 可用于使用选择器从存储中访问数据。
            ///
            /// 不必使用 StoreConnector。可以通过 `StoreProvider.of<AppState>(context)` 流式传输存储的数据,并通过 `StoreProvider.of<AppState>(context).value` 读取存储的当前快照。
            StoreConnector(
              /// 选择器将应用程序状态映射到子集以便在您的小部件中使用。
              ///
              /// 可以使用简单的函数代替 createSelector,但是 createSelector 使用记忆化来减少重建。
              selector: createSelector((AppState state, _) => state.counter),

              /// onInit 可用于在第一次构建时执行代码。这通常对于分派一个用于获取视图数据的动作很有用。
              onInit: () {
                StoreProvider.of<AppState>(context).dispatch(
                  const FetchCounterValueAction(),
                );
              },

              /// 构建器只会在选择器发出新值时重建,并且记忆化确保这只会发生在应用程序状态发生变化时。
              ///
              /// 可以使用 createSelector1、createSelector2 等组合选择器。组合选择器只有在输入选择器之一发生更改时才会发出新值。
              builder: (BuildContext context, int value) {
                return Text(
                  '$value',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 使用 StoreProvider 获取存储并调用 dispatch 方法来分派动作。
          StoreProvider.of<AppState>(context).dispatch(
            const IncrementAction(),
          );
        },
        tooltip: '递增',
        child: const Icon(Icons.add),
      ),
    );
  }
}

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

1 回复

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


在Flutter中,flutter_rx 是一个用于实现响应式编程的插件,它通常与 RxDart 库结合使用,以简化数据流的管理。下面是一个使用 flutter_rx 的简单示例,展示如何在 Flutter 应用中实现响应式编程。

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

dependencies:
  flutter:
    sdk: flutter
  rxdart: ^0.27.2  # 请根据需要选择最新版本
  flutter_rx: ^0.5.0  # 请根据需要选择最新版本

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

接下来,让我们编写一些代码来展示 flutter_rx 的使用。

示例代码

  1. 创建一个 Flutter 应用

首先,创建一个基本的 Flutter 应用结构。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Rx Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}
  1. 实现响应式逻辑

MyHomePage 中,我们将创建一个 BehaviorSubject 来管理数据流,并使用 Rx 扩展来监听变化。

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

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

class _MyHomePageState extends State<MyHomePage> {
  final _counterSubject = BehaviorSubject<int>.seeded(0);
  RxInt _counter = RxInt();

  @override
  void initState() {
    super.initState();
    // 绑定 BehaviorSubject 到 RxInt
    _counter.bindTo(_counterSubject.stream);
  }

  @override
  void dispose() {
    _counterSubject.close();
    super.dispose();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Rx 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: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

解释

  • BehaviorSubject:用于管理一个可观察的数据流,并且当新的订阅者订阅时,会立即收到最新的值。
  • RxIntflutter_rx 提供的响应式整数类型,用于监听整数值的变化。
  • bindTo:将 BehaviorSubject 的数据流绑定到 RxInt,这样当 BehaviorSubject 发出新值时,RxInt 会自动更新。
  • dispose:在 State 被销毁时,关闭 BehaviorSubject 以避免内存泄漏。

在这个示例中,当你点击浮动按钮时,_incrementCounter 方法会被调用,它会增加 BehaviorSubject 中的值。由于 _counter 已经绑定到 BehaviorSubject 的数据流,因此 _counter.value 会自动更新,并且 UI 会反映这个变化。

这就是一个使用 flutter_rx 实现响应式编程的简单示例。通过这种方式,你可以更容易地管理应用中的数据流,并自动更新 UI。

回到顶部