The way of Flutter state management

Keywords: github Big Data network REST

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

  1. 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);
          });
        },
      );
    }
    
  2. New Page

class ToDoListPage extends Page<PageState, Map<String, dynamic>> {
  ToDoListPage()
      : super(
          initState: initState,
          effect: buildEffect(),
          reducer: buildReducer(),
          view: buildView,
        );
}
  1. 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();
}
  1. 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);
  }
}
  1. 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;
}

  1. 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));
    }
  });
}
  1. 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

  1. 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;
  }

}

  1. Define Component
class ReportComponent extends Component<ReportState> {
  ReportComponent()
      : super(
          view: buildView,
        );
}
  1. 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),
          )
        ],
      ));
}
  1. 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');
  }
}
  1. 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()
              }),
        );
}
  1. 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:

  1. 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
  2. Automatically merge the reducer s of the child and synchronize the data with the store of the page
  3. 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
  4. You can share the state globally, and define a global Store associated with page.connectExtraStore

Disadvantages:

  1. More concepts, higher learning curve
  2. There are many objects and files to be defined
  3. It is easy to introduce unnecessary complexity if the scale of the project is not well grasped
  4. 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

Reference resources

  1. Getting started with fish Redux developing flutter
  2. Implementation principle of Connector
Published 14 original articles, won praise 11, visited 10000+
Private letter follow

Posted by LuAn on Sun, 19 Jan 2020 01:37:59 -0800