[From sandbox] Fish Redux - new Redux library for Flutter

[From sandbox] Fish Redux - new Redux library for Flutter


At the end of 2018, Google, not without the help of the Open-Source community, made a great gift for mobile developers by releasing the first stable version of the cross-platform framework for mobile developed by Flutter.


However, when developing large applications, a little larger than the one-page 'Hello Worlds', the developers might have faced the problem of uncertainty. How should I write an application? The framework is young enough, there is not yet a sufficient base of good open source examples, based on which one could understand the pros and cons of using different patterns, understand what to use in this particular case, and what doesn't.


The situation is saved by the fact that Flutter has a certain degree of similarity with React and React Native, which means you can learn some programming experience on the latter. Perhaps it was because of this that libraries such as Flutter Flux , Flutter Hooks , MobX , and several Redux implementations at once. For a long time, the most popular was the version of Brian Egan called Flutter Redux .


However, a couple of months ago, the first commit was seen by the library Fish Redux , published under the name of Alibaba. In a short time, the library collected a lot of popularity, already on the first day, overtaking Brian's implementation in terms of the number of stars, and on the second, twice ahead of it.


Despite its popularity, Fish has problems with documentation, which for the most part presents a description of existing classes with occasional short examples. The situation is aggravated by the fact that some of the documentation is only available in Chinese. There is another difficulty: there is almost no English-speaking issue, so relying on the experience of other developers is not easy, which is very critical, considering that only the first previews of the version are coming out.


So what’s the difference between Fish and Brian’s version? Flutter Redux is a state management framework. Fish is an application framework that places Redux at its center as the basis for managing a state. Those. Fish does several more tasks and is not limited to state management .


One of the key features of Fish Redux is to unite several reducers into larger ones through direct expression of dependencies between them, when ordinary Redux does not provide such an opportunity at all, forcing developers to implement everything on their own. But let's come back to this later, having figured out what this reducer is like and also Fish Redux itself.


Relationship between Reducer, Effect and View in the Component


image


The basis of everything in Fish Redux is the Component. This is an object that consists of three parts: Effect, Reducer and View. It is worth noting that only View is visible, i.e. Effect and Reducer are optional, the component can work without them. The component also has a current state (State).

State


For example, let's take a clicker. Let there be only one field in its state - count, which will indicate the total number of clicks.


  class ClickerState implements Cloneable & lt; ClickerState & gt;  {
  int count = 0;

  @override
  ClickerState clone () {
  return clickerState ()
  ..count = count;
  }
 }  

States must be immutable, immutable.Immunity state can be easily maintained, if you implement the interface Cloneable. In the future, when you need to create a new state, you can simply use the clone () method.


Reducer


The essence of the reducer is to respond to some action, returning a new state. The reducer should not have any side effects.


We’ll write a simple reducer that will increase the count by some number when getting the appropriate Action (about it below).


  ClickerState clickerReducer (ClickerState state, Action action) {
//The reducer receives the current state and Action in the parameters, which led to the call of this reducer.
  if (action.type == Actions.increase) {
//because  Your application will have many different actions, you need to make sure that the purpose of this is to increase Count.
  return state.clone ()
  ..count = state.count + action.payload;
//return a copy of the old state with an increase of/payload/count.
//payload is an action parameter.
  }
//if (action.type == ...) {...}//Reducer for another action can be placed here.
  return state;
 }  

Also this reducer could be written in the following form:


  Reducer & lt; ClickerState & gt;  buildClickerReducer () {
  asReducer ({
  Actions.increase: (state, action) = & gt;  state.clone () ..count = state.count + action.payload,
//Actions.anotherAction: ...
  });
 }  

Action


Action - a class in the FishRedux library that contains two fields:
Object type - action type, usually an enumeration object (enum)
dynamic payload - action parameter, optional.


Example:


  enum Actions {increase}//enumeration with action types
 class ActionsCreate {//helper class for creating them
  static Action increase (int value) = & gt;  Action (Actions.increase, payload: value);
 }  

View


The logic is ready, it remains to display the result. View is a function that takes as parameters the current state, the dispatch function, the ViewService, and returns a widget.


The dispatch function is needed to send actions: an action, the creation of which we described earlier.
ViewService contains the current BuildContext (from the standard flutter library) and provides methods for creating dependencies, but about them later.


Example:


  Widget clickerView (ClickerState state, Dispatch dispatch, ViewService viewService) {
  return RaisedButton (
  child: Text (state.count.toString ()),
  onPressed: () = & gt;  dispatch (ActionsCreate.increase (1))
//increase the number by one when you press the button
  );
 }  

Component


Putting our component together from all of this:


  class ClickerComponent extends Component & lt; ClickerState & gt;  {
  ClickerComponent (): super (
  reducer: clickerReducer,
  view: clickerView,
  );
 }  

As you can see, the effect in our example is not used, because it is not necessary. Effect - a function that must perform all side effects (side effect). But let's think up a case in which it would be impossible to do without Effect. For example, such a case could be an increase in our count by a random number from the random.org service.


Example implementation of the effect
  import 'package: http/ http.dart 'as http;//need to add http depending

 Effect & lt; ClickerState & gt;  clickerEffect () {
  return combineEffects ({
  Actions.increaseRandomly: increaseRandomly,
  });
 }

 Future & lt; void & gt;  increaseRandomly (Action action, Context & lt; ClickerState & gt; context) async {
  final response = await http.read ('https://www.random.org/integers/? num = 1 & amp; min = 1 & amp; max = 10 & amp; col = 1 & amp; base = 10 & amp; format = plain ');
//request to random.org.  Returns a random decimal number from 1 to 10.
  final value = int.parse (response);
  context.dispatch (ActionsCreate.increase (value));
 }
//Add an action increaseRandomly
 enum Actions {increase,/* new */increaseRandomly}
 class ActionsCreate {
  static Action increase (int value) = & gt;  Action (Actions.increase, payload: value);
  static Action increaseRandomly () = & gt;  const Action (Actions.increaseRandomly);//new
 }
//Add a button, when pressed, the number will increase randomly.

 Widget clickerView (ClickerState state, Dispatch dispatch, ViewService viewService) {
  return Column (
  mainAxisSize: MainAxisSize.min,
  children: [
  RaisedButton (//old button
  child: Text (state.count.toString ()),
  onPressed: () = & gt;  dispatch (ActionsCreate.increase (1))
  ),
  RaisedButton (//new
  child: const Text ('Increase randomly'),
  onPressed: () = & gt;  dispatch (ActionsCreate.increaseRandomly ())
  ),
  ]
  );
 }
//Register the effect in the component

 class ClickerComponent extends Component & lt; ClickerState & gt;  {
  ClickerComponent (): super (
  reducer: clickerReducer,
  view: clickerView,
  effect: clickerEffect ()
  );
 }  

Page


There is an extension for Component called Page & lt; T, P & gt ;. The page includes two additional fields:
T initState (P params) is a function that returns the initial state. Will be called when creating the page.
List & lt; Middleware & lt; T & gt; & gt; middleware - list of Middleware - functions that will be called before the editor.
And also one method:
Widget buildPage (P params) - which builds the page into a working widget.


Let's create the main application page:


  class MainPage extends Page & lt; void, void & gt;  {
  MainPage ():
  super (
  initState: (dynamic param) {},
  view: (state, dispatch, viewService) = & gt;  Container ()
  );
 }  

The page expands the component, which means it can include a reducer, effect and everything else that has a regular component.


In the example, an empty page was created that has neither state, nor reducers or effects. We will fix this later.


All this is in a slightly different form in Flutter Redux by Brian Egan, as well as other Redux implementations. Let's move on to the main feature of the new library - dependencies.


Dependencies


Fish Redux requires explicitly defining dependencies between components. If you want to use a subcomponent in a component, then you need not only to write these two components, but also to create a connector that will be responsible for transforming one state into another. Suppose we want to embed a ClickerComponent into the MainPage page.


First you need to add a status to our page:


  class MainState implements Cloneable & lt; MainState & gt;  {
  ClickerState clicker;

  @override
  MainState clone () {
  return MainState ()
  ..clicker = clicker;
  }

  static MainState initState (dynamic params) {
  return MainState ()
  ..clicker = ClickerState ();
  }
 }  

Now we can write Connector:


  class ClickerConnector extends ConnOp & lt; MainState, ClickerState & gt;  {
  @override
  ClickerState get (MainState state) = & gt;  state.clicker;

//This method will be called when the state of the child component changes.
  @override
  void set (MainState state, ClickerState subState) = & gt;  state.clicker = subState;
 }  

Everything. Everything is ready to add our component:


  class MainPage extends Page & lt; MainState, void & gt;  {
  MainPage ():
  super (
  initState: MainState.initState,
  dependencies: Dependencies (
  slots: {
  'clicker': ClickerComponent ().asDependent (ClickerConnector ()),
//can be written as
//'clicker': ClickerComponent () + ClickerConnector (),
  },
  ),
  view: (state, dispatch, viewService) {
//get our clicker widget.
  final clickerWidget = viewService.buildComponent ('clicker');

  return Scaffold (
  body: Column (
  mainAxisAlignment: MainAxisAlignment.center,
  mainAxisSize: MainAxisSize.max,
  children: [
  Center (
  child: clickerWidget,//display it
  )
  ],
  )
  );
  },
  );
 }  

So now you can build a complete working application by adding the following code to main.dart :


 
 void main () = & gt;  runApp (MyApp ());

 class MyApp extends StatefulWidget {
  @override
  _MyAppState createState () = & gt;  _MyAppState ();
 }

 class _MyAppState extends State & lt; MyApp & gt;  {
  @override
  Widget build (BuildContext context) = & gt;
  MaterialApp (home: MainPage (). BuildPage (null));
 }  

All code divided by files is available here . Have a good development experience with Flutter.

Source text: [From sandbox] Fish Redux - new Redux library for Flutter