github:
https://github.com/brianegan/flutter_redux
demo:
效果:
github_search
使用步骤 :
代码见counter
1、 增加依赖flutter_redux
2、 定义action
// One simple action: Increment
enum Actions { Increment }
3、 定义reducer
// 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;
}
4、 定义store,通过StoreProvider,将store放在顶层widget
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,
));
}
store通过stream实现reducer响应action
/// By default, the Stream is async.
Store(
this.reducer, {
State initialState,
List<Middleware<State>> middleware = const [],
bool syncStream = false,
/// If set to true, the Store will not emit onChange events if the new State
/// that is returned from your [reducer] in response to an Action is equal
/// to the previous state.
///
/// Under the hood, it will use the `==` method from your State class to
/// determine whether or not the two States are equal.
bool distinct = false,
}) : _changeController = StreamController.broadcast(sync: syncStream) {
_state = initialState;
_dispatchers = _createDispatchers(
middleware,
_createReduceAndNotify(distinct),
);
}
StoreProvider 继承于InheritedWidget,通知被依赖的子Widget更新
class StoreProvider<S> extends InheritedWidget {
final Store<S> _store;
/// Create a [StoreProvider] by passing in the required [store] and [child]
/// parameters.
const StoreProvider({
Key key,
@required Store<S> store,
@required Widget child,
}) : assert(store != null),
assert(child != null),
_store = store,
super(key: key, child: child);
/// A method that can be called by descendant Widgets to retrieve the Store
/// from the StoreProvider.
///
/// Important: When using this method, pass through complete type information
/// or Flutter will be unable to find the correct StoreProvider!
///
/// ### Example
///
/// ```
/// class MyWidget extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// final store = StoreProvider.of<int>(context);
///
/// return Text('${store.state}');
/// }
/// }
/// ```
///
/// If you need to use the [Store] from the `initState` function, set the
/// [listen] option to false.
///
/// ### Example
///
/// ```
/// class MyWidget extends StatefulWidget {
/// static GlobalKey<_MyWidgetState> captorKey = GlobalKey<_MyWidgetState>();
///
/// MyWidget() : super(key: captorKey);
///
/// _MyWidgetState createState() => _MyWidgetState();
/// }
///
/// class _MyWidgetState extends State<MyWidget> {
/// Store<String> store;
///
/// @override
/// void initState() {
/// super.initState();
/// store = StoreProvider.of<String>(context, listen: false);
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Container();
/// }
/// }
/// ```
static Store<S> of<S>(BuildContext context, {bool listen = true}) {
final type = _typeOf<StoreProvider<S>>();
final provider = (listen
? context.inheritFromWidgetOfExactType(type)
: context
.ancestorInheritedElementForWidgetOfExactType(type)
?.widget) as StoreProvider<S>;
if (provider == null) throw StoreProviderError(type);
return provider._store;
}
// Workaround to capture generics
static Type _typeOf<T>() => T;
@override
bool updateShouldNotify(StoreProvider<S> oldWidget) =>
_store != oldWidget._store;
}
5、子widget通过StoreConnector获取store,StreamBuilder监听store变化,重建widget
const StoreConnector({
Key key,
@required this.builder,
@required this.converter,
this.distinct = false,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
this.ignoreChange,
this.onWillChange,
this.onDidChange,
this.onInitialBuild,
}) : assert(builder != null),
assert(converter != null),
super(key: key);
@override
Widget build(BuildContext context) {
return _StoreStreamListener<S, ViewModel>(
store: StoreProvider.of<S>(context),
builder: builder,
converter: converter,
distinct: distinct,
onInit: onInit,
onDispose: onDispose,
rebuildOnChange: rebuildOnChange,
ignoreChange: ignoreChange,
onWillChange: onWillChange,
onDidChange: onDidChange,
onInitialBuild: onInitialBuild,
);
}
}
// Listens to the [Store] and calls [builder] whenever [store] changes.
class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
final ViewModelBuilder<ViewModel> builder;
final StoreConverter<S, ViewModel> converter;
final Store<S> store;
final bool rebuildOnChange;
final bool distinct;
final OnInitCallback<S> onInit;
final OnDisposeCallback<S> onDispose;
final IgnoreChangeTest<S> ignoreChange;
final OnWillChangeCallback<ViewModel> onWillChange;
final OnDidChangeCallback<ViewModel> onDidChange;
final OnInitialBuildCallback<ViewModel> onInitialBuild;
const _StoreStreamListener({
Key key,
@required this.builder,
@required this.store,
@required this.converter,
this.distinct = false,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
this.ignoreChange,
this.onWillChange,
this.onDidChange,
this.onInitialBuild,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _StoreStreamListenerState<S, ViewModel>();
}
}
class _StoreStreamListenerState<S, ViewModel>
extends State<_StoreStreamListener<S, ViewModel>> {
Stream<ViewModel> stream;
ViewModel latestValue;
@override
void initState() {
if (widget.onInit != null) {
widget.onInit(widget.store);
}
if (widget.onInitialBuild != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onInitialBuild(latestValue);
});
}
latestValue = widget.converter(widget.store);
_createStream();
super.initState();
}
@override
void dispose() {
if (widget.onDispose != null) {
widget.onDispose(widget.store);
}
super.dispose();
}
@override
void didUpdateWidget(_StoreStreamListener<S, ViewModel> oldWidget) {
latestValue = widget.converter(widget.store);
if (widget.store != oldWidget.store) {
_createStream();
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return widget.rebuildOnChange
? StreamBuilder<ViewModel>(
stream: stream,
builder: (context, snapshot) => widget.builder(
context,
latestValue,
),
)
: widget.builder(context, latestValue);
}
ViewModel _mapConverter(S state) {
return widget.converter(widget.store);
}
bool _whereDistinct(ViewModel vm) {
if (widget.distinct) {
return vm != latestValue;
}
return true;
}
bool _ignoreChange(S state) {
if (widget.ignoreChange != null) {
return !widget.ignoreChange(state);
}
return true;
}
void _createStream() {
stream = widget.store.onChange
.where(_ignoreChange)
.map(_mapConverter)
// Don't use `Stream.distinct` because it cannot capture the initial
// ViewModel produced by the `converter`.
.where(_whereDistinct)
// After each ViewModel is emitted from the Stream, we update the
// latestValue. Important: This must be done after all other optional
// transformations, such as ignoreChange.
.transform(StreamTransformer.fromHandlers(handleData: _handleChange));
}
void _handleChange(ViewModel vm, EventSink<ViewModel> sink) {
if (widget.onWillChange != null) {
widget.onWillChange(latestValue, vm);
}
latestValue = vm;
if (widget.onDidChange != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onDidChange(latestValue);
});
}
sink.add(vm);
}
}
拓展 :
代码见github_search
void main() {
final store = Store<SearchState>(
searchReducer,
initialState: SearchInitial(),
middleware: [
// The following middleware both achieve the same goal: Load search
// results from github in response to SearchActions.
//
// One is implemented as a normal middleware, the other is implemented as
// an epic for demonstration purposes.
SearchMiddleware(GithubClient()),
// EpicMiddleware<SearchState>(SearchEpic(GithubClient())),
],
);
1、combineReducers
combineReducers将action和函数绑定起来,将Reduce里面的代码解耦,整洁清晰
class SearchLoadingAction {}
class SearchErrorAction {}
class SearchResultAction {
final SearchResult result;
SearchResultAction(this.result);
}
/// Reducer
final searchReducer = combineReducers<SearchState>([
TypedReducer<SearchState, SearchLoadingAction>(_onLoad),
TypedReducer<SearchState, SearchErrorAction>(_onError),
TypedReducer<SearchState, SearchResultAction>(_onResult),
]);
SearchState _onLoad(SearchState state, SearchLoadingAction action) =>
SearchLoading();
SearchState _onError(SearchState state, SearchErrorAction action) =>
SearchError();
SearchState _onResult(SearchState state, SearchResultAction action) =>
action.result.items.isEmpty
? SearchEmpty()
: SearchPopulated(action.result);
2、中间件 middleware
中间件middleware,中间件类似拦截器,实现MiddlewareClass。SearchAction发送前,延时250毫秒进行查询
class SearchMiddleware implements MiddlewareClass<SearchState> {
final GithubClient api;
Timer _timer;
CancelableOperation<Store<SearchState>> _operation;
SearchMiddleware(this.api);
@override
void call(Store<SearchState> store, dynamic action, NextDispatcher next) {
if (action is SearchAction) {
// Stop our previous debounce timer and search.
_timer?.cancel();
_operation?.cancel();
// Don't start searching until the user pauses for 250ms. This will stop
// us from over-fetching from our backend.
_timer = Timer(Duration(milliseconds: 250), () {
store.dispatch(SearchLoadingAction());
// Instead of a simple Future, we'll use a CancellableOperation from the
// `async` package. This will allow us to cancel the previous operation
// if a Search term comes in. This will prevent us from
// accidentally showing stale results.
_operation = CancelableOperation.fromFuture(api
.search(action.term)
.then((result) => store..dispatch(SearchResultAction(result)))
.catchError((e, s) => store..dispatch(SearchErrorAction())));
});
}
// Make sure to forward actions to the next middleware in the chain!
next(action);
}
}