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一起使用。
伴随库
- flipperkit_redux_middleware - Flutter Redux应用的Redux Inspector(使用Flutter Debugger)
- flutter_redux_dev_tools - Flutter Redux应用的时间旅行开发工具
- redux_persist - 持久化Redux状态
- flutter_redux_navigation - 使用redux事件进行导航
- flutter_redux_gen - VS Code扩展,用于生成redux代码。
用法
让我们用有史以来最喜欢的计数器示例来演示基本用法!
注意:此示例需要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重建的内容!
因此,对于此类更复杂的场景,redux和redux_flutter应运而生。
它提供了一组工具,允许您的小部件以一种天真的方式dispatch操作
,然后将业务逻辑写在另一个地方,该地方将接受
这些操作并以安全、可测试的方式更新ShoppingCart。
更重要的是,一旦ShoppingCart在Store中更新,Store
将发出onChange事件。这使您可以收听Store更新并在
更改时在正确的位置重建UI!现在,您可以将您的
业务逻辑与UI逻辑分离,以一种可测试、可观察的方式,而无需
手动连接一堆东西!
Android中的类似模式是MVP模式,或使用Rx Observables来
管理View的状态。
flutter_redux仅负责将您的Store传递给所有
后代StoreConnector小部件。如果您的状态发出了更改事件,那么只有StoreConnector小部件及其后代将被自动重建
并获得Store的最新状态!
这使您可以专注于应用程序的外观和工作方式,而无需考虑
连接所有内容的粘合代码!