flutter_redux

一组实用工具,让您可以轻松地消费 Redux Store 来构建 Flutter 小部件。

该包支持空安全,并且构建为与Redux.dart 5.0.0+一起使用。

Redux 小部件

  • StoreProvider - 基础小部件。它会将给定的Redux Store传递给所有请求它的后代。
  • StoreBuilder - 一个后代小部件,它从StoreProvider获取Store,并将其传递给小部件builder函数。
  • StoreConnector - 一个后代小部件,它从最近的StoreProvider祖先获取Store,使用给定的converter函数将Store转换为ViewModel,并将ViewModel传递给builder函数。每当Store发出更改事件时,小部件都会自动重建。无需管理订阅!

示例

  • 简单的例子 - Flutter标准“计数器按钮”示例的移植
  • Github搜索 - 一个演示如何搜索用户输入的内容的示例,演示了中间件和Epic方法。
  • 待办事项应用 - 一个更完整的示例,包含持久化、路由和嵌套状态。
  • Timy Messenger - 一个大型开源应用,它将flutter_redux与Firebase Firestore一起使用。

伴随库

用法

让我们用有史以来最喜欢的计数器示例来演示基本用法!

注意:此示例需要flutter_redux 0.4.0+和Dart 2!如果您使用的是
Dart 1,请查看旧的
示例

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

// One simple action: Increment
enum Actions { Increment }

// The reducer, which takes the previous count and increments it in response
// to an Increment action.
int counterReducer(int state, dynamic action) {
  if (action == Actions.Increment) {
    return state + 1;
  }

  return state;
}

void main() {
  // Create your store as a final variable in the main function or inside a
  // State object. This works better with Hot Reload than creating it directly
  // in the `build` function.
  final store = Store<int>(counterReducer, initialState: 0);

  runApp(FlutterReduxApp(
    title: 'Flutter Redux Demo',
    store: store,
  ));
}

class FlutterReduxApp extends StatelessWidget {
  final Store<int> store;
  final String title;

  FlutterReduxApp({Key key, this.store, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // The StoreProvider should wrap your MaterialApp or WidgetsApp. This will
    // ensure all routes have access to the store.
    return StoreProvider<int>(
      // Pass the store to the StoreProvider. Any ancestor `StoreConnector`
      // Widgets will find and use this value as the `Store`.
      store: store,
      child: MaterialApp(
        theme: ThemeData.dark(),
        title: title,
        home: Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Connect the Store to a Text Widget that renders the current
                // count.
                //
                // We'll wrap the Text Widget in a `StoreConnector` Widget. The
                // `StoreConnector` will find the `Store` from the nearest
                // `StoreProvider` ancestor, convert it into a String of the
                // latest count, and pass that String  to the `builder` function
                // as the `count`.
                //
                // Every time the button is tapped, an action is dispatched and
                // run through the reducer. After the reducer updates the state,
                // the Widget will be automatically rebuilt with the latest
                // count. No need to manually manage subscriptions or Streams!
                StoreConnector<int, String>(
                  converter: (store) => store.state.toString(),
                  builder: (context, count) {
                    return Text(
                      'The button has been pushed this many times: $count',
                      style: Theme.of(context).textTheme.display1,
                    );
                  },
                )
              ],
            ),
          ),
          // Connect the Store to a FloatingActionButton. In this case, we'll
          // use the Store to build a callback that will dispatch an Increment
          // Action.
          //
          // Then, we'll pass this callback to the button's `onPressed` handler.
          floatingActionButton: StoreConnector<int, VoidCallback>(
            converter: (store) {
              // Return a `VoidCallback`, which is a fancy name for a function
              // with no parameters and no return value. 
              // It only dispatches an Increment action.
              return () => store.dispatch(Actions.Increment);
            },
            builder: (context, callback) {
              return FloatingActionButton(
                // Attach the `callback` to the `onPressed` attribute
                onPressed: callback,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              );
            },
          ),
        ),
      ),
    );
  }
}

目的

一个合理的人可能会问的问题
如果存在StatefulWidget,为什么还需要所有这些?

我的建议与原始Redux.JS作者相同:如果您有一个简单的
应用,请使用最简单的东西。在Flutter中,StatefulWidget非常适合
简单的计数器应用。

但是,假设您有一个更复杂的应用程序,例如带有购物车
的电子商务应用程序。购物车应出现在应用程序的多个屏幕上
并应由这些不同屏幕上的许多不同类型的小部件进行更新
(例如,“添加到购物车”小部件在所有产品屏幕上,“从购物车中删除
项目”小部件在购物车屏幕上,“更改数量”小部件等)。

此外,您绝对希望测试此逻辑,因为它是您应用程序的核心业务
逻辑!

现在,在这种情况下,您可以创建一个可测试的ShoppingCart类作为
单例,或者创建一个根StatefulWidget,它将ShoppingCart向下
向下向下
传递到小部件层次结构中,传递到“添加到购物车”或“从购物车中删除
购物车”小部件。

单例可能对测试不利,而Flutter还没有一个很好的
依赖注入库(例如Dagger2),所以我宁愿避免
这些。

但是,将ShoppingCart到处传递会变得很混乱。这也意味着
将“添加项目”按钮移到新位置会更加困难,因为您需要
更新您应用程序中传递状态的各个小部件。

此外,您需要一种方法来观察ShoppingCart何时更改,以便
在更改时重建小部件(例如,从“添加”按钮到“已添加”
按钮)。

一种处理方法是,每当ShoppingCart更改时,只需在根小部件中
调用setState,但这将要求根小部件下的整个应用程序
进行重建!Flutter很快,但我们应该明智地考虑
我们要求Flutter重建的内容!

因此,对于此类更复杂的场景,reduxredux_flutter应运而生。
它提供了一组工具,允许您的小部件以一种天真的方式dispatch操作
,然后将业务逻辑写在另一个地方,该地方将接受
这些操作并以安全、可测试的方式更新ShoppingCart

更重要的是,一旦ShoppingCartStore中更新,Store
将发出onChange事件。这使您可以收听Store更新并在
更改时在正确的位置重建UI!现在,您可以将您的
业务逻辑与UI逻辑分离,以一种可测试、可观察的方式,而无需
手动连接一堆东西!

Android中的类似模式是MVP模式,或使用Rx Observables来
管理View的状态。

flutter_redux仅负责将您的Store传递给所有
后代StoreConnector小部件。如果您的状态发出了更改事件,那么只有
StoreConnector小部件及其后代将被自动重建
并获得Store的最新状态!

这使您可以专注于应用程序的外观和工作方式,而无需考虑
连接所有内容的粘合代码!

GitHub

https://github.com/brianegan/flutter_redux