Pick up another article. The way of Flutter state management (4)
This article mainly introduces the shuttle ﹣ mobx
Fish Redux
Version: 0.2.7
Library address: https://github.com/alibaba/fish-redux/
Evolution process
concept
object | Explain | Subordinate Library |
---|---|---|
Action | Represents an intent that contains two fields type,payload |
|
Connector | It expresses how to read small data from a big data, At the same time, how to synchronize the modification of small data to big data, such a data connection relationship |
|
Reducer | A context free pure function | |
State | State value | |
Middleware | Middleware, injecting logic in AOP aspect oriented form | |
Component | Encapsulation of view presentation and logic functions | |
Effect | Dealing with side effects of Action | |
Dependent | Express how the widget | small adapter is connected to the large Component | |
Page | Inherit Component, for page level abstraction, build a Store (sub Component share) |
Use
Source official Todos
-
Entry route configuration
///Create application's root Widget ///1. Create a simple route and register the page ///2. Connect the required page to the app store ///3. Enhance the AOP of the required pages Widget createApp() { final AbstractRoutes routes = PageRoutes( pages: <String, Page<Object, dynamic>>{ ///Register TodoList main page 'todo_list': ToDoListPage(), }, visitor: (String path, Page<Object, dynamic> page) { ///Only a specific range of pages need to establish a connection with the app store ///Meet page < T >, t is a subclass of GlobalBaseState if (page.isTypeof<GlobalBaseState>()) { ///Establish one-way data connection of app store driving PageStore ///1. Parameter 1 AppStore ///2. Parameter 2 how to change PageStore.state when the AppStore.state changes page.connectExtraStore<GlobalState>(GlobalStore.store, (Object pagestate, GlobalState appState) { ///Change pagestate according to appState return pagestate; }); } /// AOP ///Pages can have some private AOP enhancements, but often there will be some AOP for the entire application, all pages will have. ///These common AOP s are added by traversing the routing page. page.enhancer.append( ... /// Store AOP middleware: <Middleware<dynamic>>[ logMiddleware<dynamic>(tag: page.runtimeType.toString()), ], ); }, ); return MaterialApp( title: 'Fluro', home: routes.buildPage('todo_list', null), onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<Object>(builder: (BuildContext context) { return routes.buildPage(settings.name, settings.arguments); }); }, ); }
-
New Page
class ToDoListPage extends Page<PageState, Map<String, dynamic>> { ToDoListPage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, ); }
- Define state
class PageState extends MutableSource implements GlobalBaseState, Cloneable<PageState> { List<ToDoState> toDos; @override Color themeColor; @override PageState clone() { return PageState() ..toDos = toDos ..themeColor = themeColor; } @override Object getItemData(int index) => toDos[index]; @override String getItemType(int index) => 'toDo'; @override int get itemCount => toDos?.length ?? 0; @override void setItemData(int index, Object data) => toDos[index] = data; } PageState initState(Map<String, dynamic> args) { //just demo, do nothing here... return PageState(); }
- Define Action
enum PageAction { initToDos, onAdd } class PageActionCreator { static Action initToDosAction(List<ToDoState> toDos) { return Action(PageAction.initToDos, payload: toDos); } static Action onAddAction() { return const Action(PageAction.onAdd); } }
- Define Reducer
Reducer<PageState> buildReducer() { return asReducer( <Object, Reducer<PageState>>{PageAction.initToDos: _initToDosReducer}, ); } PageState _initToDosReducer(PageState state, Action action) { final List<ToDoState> toDos = action.payload ?? <ToDoState>[]; final PageState newState = state.clone(); newState.toDos = toDos; return newState; }
- Define Effect
Effect<PageState> buildEffect() { return combineEffects(<Object, Effect<PageState>>{ Lifecycle.initState: _init, PageAction.onAdd: _onAdd, }); } void _init(Action action, Context<PageState> ctx) { final List<ToDoState> initToDos = <ToDoState>[]; ///Can be used for network / IO and other time-consuming operations ctx.dispatch(PageActionCreator.initToDosAction(initToDos)); } void _onAdd(Action action, Context<PageState> ctx) { Navigator.of(ctx.context) .pushNamed('todo_edit', arguments: null) .then((dynamic toDo) { if (toDo != null && (toDo.title?.isNotEmpty == true || toDo.desc?.isNotEmpty == true)) { ctx.dispatch(list_action.ToDoListActionCreator.add(toDo)); } }); }
- Define View view
Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) { return Scaffold( appBar: AppBar( backgroundColor: state.themeColor, ///Get state status title: const Text('ToDoList'), ), body: Container( child: Column( children: <Widget>[ ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => dispatch(PageActionCreator.onAddAction()), ///Send out intention to change state tooltip: 'Add', child: const Icon(Icons.add), ), ); }
Assemble subcomponent
- Define state
class ReportState implements Cloneable<ReportState> { int total; int done; ReportState({this.total = 0, this.done = 0}); @override ReportState clone() { return ReportState() ..total = total ..done = done; } }
- Define Component
class ReportComponent extends Component<ReportState> { ReportComponent() : super( view: buildView, ); }
- Definition view
Widget buildView( ReportState state, Dispatch dispatch, ViewService viewService, ) { return Container( margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0), color: Colors.blue, child: Row( children: <Widget>[ Container( child: const Icon(Icons.report), margin: const EdgeInsets.only(right: 8.0), ), Text( 'Total ${state.total} tasks, ${state.done} done.', style: const TextStyle(fontSize: 18.0, color: Colors.white), ) ], )); }
- Define Connector to connect parent-child Component
class ReportConnector extends ConnOp<PageState, ReportState> with ReselectMixin<PageState, ReportState> { @override ReportState computed(PageState state) { return ReportState() ..done = state.toDos.where((ToDoState tds) => tds.isDone).length ..total = state.toDos.length; } @override void set(PageState state, ReportState subState) { throw Exception('Unexcepted to set PageState from ReportState'); } }
- The page is used in page, and the modified page is as follows
class ToDoListPage extends Page<PageState, Map<String, dynamic>> { ToDoListPage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, dependencies: Dependencies<PageState>( slots: <String, Dependent<PageState>>{ 'report': ReportConnector() + ReportComponent() }), ); }
- The buildView transformation of page is as follows
Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) { final ListAdapter adapter = viewService.buildAdapter(); return Scaffold( appBar: AppBar( backgroundColor: state.themeColor, title: const Text('ToDoList'), ), body: Container( child: Column( children: <Widget>[ viewService.buildComponent('report'), ///Load subcomponent ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => dispatch(PageActionCreator.onAddAction()), tooltip: 'Add', child: const Icon(Icons.add), ), ); }
Key objects
Middleware
StoreMiddleware is actually to strengthen the dispatch function of the Store
/// fish-redux-master/lib/src/redux/apply_middleware.dart StoreEnhancer<T> applyMiddleware<T>(List<Middleware<T>> middleware) { return middleware == null || middleware.isEmpty ? null : (StoreCreator<T> creator) => (T initState, Reducer<T> reducer) { final Store<T> store = creator(initState, reducer); final Dispatch initialValue = store.dispatch; ///Original dispatch store.dispatch = middleware .map((Middleware<T> middleware) => middleware( ///Execute the outermost function of middleware to return composable < T >, which is used here to wrap the dispatch dispatch: (Action action) => store.dispatch(action), getState: store.getState, )) .fold( initialValue, (Dispatch previousValue, Dispatch Function(Dispatch) element) => element(previousValue), ///Each time the previous dispatch is passed in, a new dispatch is returned. With the closure, the new dispatch holds the reference of the previous dispatch ); return store; }; }
An example of Middleware is as follows:
Middleware<T> logMiddleware<T>({ String tag = 'redux', String Function(T) monitor, }) { return ({Dispatch dispatch, Get<T> getState}) { return (Dispatch next) { ///This method is the element of the fold method in the previous schematic code segment return isDebug() ? (Action action) { ///Return the packaged Dispatch print('---------- [$tag] ----------'); print('[$tag] ${action.type} ${action.payload}'); final T prevState = getState(); if (monitor != null) { print('[$tag] prev-state: ${monitor(prevState)}'); } next(action); final T nextState = getState(); if (monitor != null) { print('[$tag] next-state: ${monitor(nextState)}'); } } : next; }; }; }
Global Store
page.connectExtraStore<GlobalState>(GlobalStore.store, (Object pagestate, GlobalState appState) { final GlobalBaseState p = pagestate; if (p.themeColor != appState.themeColor) { if (pagestate is Cloneable) { final Object copy = pagestate.clone(); final GlobalBaseState newState = copy; newState.themeColor = appState.themeColor; return newState; } } return pagestate; });
Connector
abstract class MutableConn<T, P> implements AbstractConnector<T, P> { const MutableConn(); void set(T state, P subState); @override SubReducer<T> subReducer(Reducer<P> reducer) { ///Package the reducer of this Component into a new reducer to inject into the parent store return (T state, Action action, bool isStateCopied) { final P props = get(state); if (props == null) { return state; } final P newProps = reducer(props, action); ///Call the reducer of this Component to return the state of the child final bool hasChanged = newProps != props; final T copy = (hasChanged && !isStateCopied) ? _clone<T>(state) : state; if (hasChanged) { set(copy, newProps); ///Notify parent Component synchronization status } return copy; }; } }
See the official document for the rest: https://github.com/alibaba/fish-redux/blob/master/doc/README-cn.md
summary
Advantage:
- Each Page has a Store, and its sub components share its Store. A single Component still has the features of redux to achieve divide and conquer
- Automatically merge the reducer s of the child and synchronize the data with the store of the page
- Use event bus to establish the connection between pages, and use broadcast effect to distribute the actions that page itself does not care about to other pages
- You can share the state globally, and define a global Store associated with page.connectExtraStore
Disadvantages:
- More concepts, higher learning curve
- There are many objects and files to be defined
- It is easy to introduce unnecessary complexity if the scale of the project is not well grasped
- Code structure is more intrusive
To be continued
There are a lot of concepts defined in the fish Redux framework, which need to be further developed