This paper is relatively slag, to be reconstructed....

Today’s task is to redux yesterday’s code.

Before that, it is said to unify several nouns in this article. See github for the source of this article

-Leslie: Store dispatch action reducer connector provider to Converter Builder dependency Flutter_redux: ^ 0.5.3Copy the code

1. Redux the initial project

Everyone should still remember the initial project, the following is its carding diagram, sharpening the knife is not wrong to cut wood workers.

I’m going to start with that and give you a quick overview of what redux is.


1.1: Analyze behavior and change

Quite simply, the behavior is the click, and the change is the growth of the number.

Reducer is not pure or impure. In my opinion, it is an independent logical unit that can exist logically without relying on the outside world: given an input, an expected output will be returned

Enum Actions {increment// Define add Actions} // Use counterReducer to reduce Actions from class. Int counterReducer(int input, dynamic action) {var output; switch(action){ case Actions.increment: output=input+1; break; } return output; }Copy the code

1.2: Create the ReduxPage component

One of the cores of Redux is the Store, which is a repository for storing, supplying and distributing.

Returns a repository provider, which is a Widget that requires the Store and Child attributes.

class ReduxPage extends StatelessWidget {
  final Store<int> store;
  ReduxPage({Key key, this.store,}) : super(key: key);
  
  return  StoreProvider<int>(
    store: store,
    child: child,
  );
}
Copy the code

1.3: Now the focus is on how children are constructed

For clarity, separate countText and Fab from the state-related components

var child = MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text("Flutter Redux Demo"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [Text('You have pushed the button this many times'), countText// show digit Text],),), floatingActionButton: Fab,// click the button),);Copy the code
  • Text:countText to display the number

If you want to get something from the warehouse, what do you need? The key to bai. The StoreConnector warehouse connector is this key

The Converter calls back the Store object, which you can then value through the store and return through the constructor

Var countText= StoreConnector<int, String>(Converter: (store) => store.state.toString(), (context, count) {// Constructor, build Widget return Text(count, style: theme.of (context).textTheme.display1,); });Copy the code
  • Buttons that handle actions

The processing action also requires a warehouse, which is used to dispatch corresponding actions.

In the constructor, you can use the action logic.

var fab= StoreConnector<int, VoidCallback>( converter: (store) { return () => store.dispatch(Actions.increment); }, Builder: (Context, callback) {return FloatingActionButton(onPressed: callback, Tooltip: 'Increment', child: Icon(Icons.add), ); });Copy the code

Here, the action side is the attack side and the response side is the receiver side. The figure below illustrates how the two widgets are built


1.4: Construction of warehouse objects

So the core of this is the repository store, so how do you generate objects

void main() {
  final store =  Store<int>(counterReducer, initialState: 0);
  runApp(ReduxPage(
    store: store,
  ));
}
Copy the code

2. The story

You might say, “It doesn’t feel good either. It feels like a hassle.”

2.1: When adding a feature

What if I want to add 10? With Redux you need to define a behavior and a response.

Modify the behavior when the behavior is distributed. Maybe you said I don’t need redux, just change careers. What if there’s a lot of logic and then you have to change it back? It is very convenient to abstract out a behavior to manage the logic switch and the modification can be done directly in the Reducer, avoiding contamination of the packaged component source code.

enum Actions{
  increment,
  increment10
}

int counterReducer(int input, dynamic action) {
  var output;
  switch(action){
    case Actions.increment:
      output=input+1;
      break;
    case Actions.increment10:
      output=input+10;
      break;
  }
  return output;
}

var fab= StoreConnector<int, VoidCallback>(
      converter: (store) {
        return () => store.dispatch(Actions.increment10);
      },
Copy the code

2.2: Global state sharing

How easily another interface can enjoy the data of the previous interface is a big problem.

Of course, you can pass parameters by constructing, but this is obviously messy and takes in parameters.

import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; class SecondPage extends StatelessWidget { SecondPage({Key key}) : super(key: key); @override Widget build(BuildContext context) {var text = StoreConnector<int, String> (store) => store.state.toString(), builder: (context, count) { return Text( count.toString(), style: Theme.of(context).textTheme.display1, ); }); return Scaffold( appBar: AppBar( title: Text("SecondPage"), ), body: Align( alignment: Alignment.topCenter, child: text, ), ); }}Copy the code
  • Add text to ReduxPage by clicking to jump to SecondPage
Builder _skipToSecondPage(StoreConnector<int, String> countText) { return Builder( builder: (context) => InkWell(child: countText, onTap: () { Navigator.of(context) .push(MaterialPageRoute(builder: (BuildContext context) { return SecondPage(); })); },),); }Copy the code

3. A remake of yesterday’s TodoList

It’s the same interface.


3.1: Define the Todo description class
class Todo { String sth; Bool done; // Todo({this.sth, this.done}); // Is that done?}Copy the code

3.2: Define state classes and actions and changes

Yesterday we analyzed one with three states and four actions

class TodoState { List<Todo> todos; // List data String text; ShowType ShowType; TodoState({this.todos, this.text, this.showType}); // display type} Acts {add, // add to todo selectAll, // selectAll selectTodo, // selectAll selectDone, } TodoState todoReducer(TodoState input, dynamic action) {switch (action) {case Acts. Add: if (input.text! = null && input.text ! = "") { input.todos.add(Todo(sth: input.text, done: false)); input.text = ""; } break; case Acts.selectAll: input.showType=ShowType.all; break; case Acts.selectTodo: input.showType=ShowType.todo; break; case Acts.selectDone: input.showType=ShowType.done; break; } return input; } final todoListStore = Store<TodoState>(todoReducer, initialState:// initialState) "", showType: ShowType.all));Copy the code

3.3: Component encapsulation

As above, use StoreConnector to get the resource from the repository, except that the resource is a TodoState object

If you move, as the attacker, you still call back to perform the corresponding move.

class TodoList extends StatefulWidget { final Store<TodoState> store; TodoList({ Key key, this.store, }) : super(key: key); @override _TodoListState createState() => _TodoListState(); } class _TodoListState extends State<TodoList> { @override Widget build(BuildContext context) { var textField= StoreConnector<TodoState, TodoState>(Converter: (store) =>store.state,// (context, state) {// Constructor, build Widget return TextField(Controller: TextEditingController(text: state.text), keyboardType: TextInputType.text, textAlign: TextAlign.start, maxLines: 1, cursorColor: Colors.black, cursorWidth: 3, style: TextStyle( fontSize: 16, color: Colors.lightBlue, backgroundColor: Colors.white), decoration: InputDecoration( filled: True, fillColor: colors. white, hintText: 'Add a todo item ', hintStyle: TextStyle(color: colors. black26, fontSize: ContentPadding: EdgeInsets. Only (left: 14.0, bottom: 8.0, top: 8.0), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.white), borderRadius: BorderRadius.only( topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)), ), enabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: Colors.white), borderRadius: BorderRadius.only( topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)), ), ), onChanged: (str){ state.text=str; }); }); var btn = StoreConnector<TodoState, VoidCallback>( converter:(store) { return () => store.dispatch(Acts.add); }, Builder: (context, callback) {return RaisedButton(child: Icon(icon.add), padding: EdgeInsets.zero, onPressed: (){ callback(); FocusScope.of(context).requestFocus(FocusNode()); }); }); var inputBtn = Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( child: textField, width: 200, ), ClipRRect( borderRadius: BorderRadius.only( topRight: Radius.circular(10), bottomRight: Radius.circular(10)), child: Container( child: btn, width: 36, height: 36, ), ), ], ); Var listInfo = [[" all ", Acts. SelectAll], [" done ", Acts. SelectTodo],]; Var op = Row (. / / operation button mainAxisAlignment: mainAxisAlignment spaceEvenly, children: listInfo.map((e) { return StoreConnector<TodoState, VoidCallback>( converter: (store) { return () => store.dispatch(e[1]); }, builder: (context, callback) { return RaisedButton( onPressed: callback, child: Text(e[0]), color: Colors.blue, ); }); }).toList(), ); Var listView = StoreConnector<TodoState, TodoState>(Converter: (store) => store.state, // (context, state) { var result; // Constructor, build Widget switch(state.showtype){case showType. All: result= formList(state.todos); break; case ShowType.todo: result= formList(List.of( state.todos.where((e)=>! e.done))); break; case ShowType.done: result= formList(List.of( state.todos.where((e)=>e.done))); break; } return result; }); return StoreProvider<TodoState>( store: widget.store, child: Column( children: <Widget>[inputBtn, op, Expanded(child: listView)], ), ); } Widget formList(List<Todo> todos) { return ListView.builder( itemCount: todos.length, padding: EdgeInsets. All (8.0), itemExtent: 50.0, itemBuilder: (BuildContext context, int index) {var key = todos[index]. var value = todos[index].done; var text = Align( child: Text( key, style: TextStyle( decorationThickness: 3, decoration: value ? TextDecoration.lineThrough : TextDecoration.none, decorationColor: Colors.blue), ), alignment: Alignment.centerLeft, ); return Card( child: Row( children: <Widget>[ Checkbox( onChanged: (b) { todos[index].done = b; setState(() {}); }, value: todos[index].done, ), text ], ), ); }); }}Copy the code

3.3: Use of ViewModel

You can see that there are two generics in StoreConnector, the second of which is named ViewModel

Now it is very convenient to implement that the CheckBox is hidden after the completed and incomplete buttons are clicked.

-->[1. Add a state]---- class TodoState {List<Todo> todos; // List data String text; ShowType ShowType; // Display type bool showBox=true; / / display the checkBox TodoState ({this. Todos, enclosing text, enclosing showType, enclosing showBox}); ---- case Acts. SelectAll: input.showBox=true; input.showType=ShowType.all; break; case Acts.selectTodo: input.showType=ShowType.todo; input.showBox=false; break; case Acts.selectDone: input.showType=ShowType.done; input.showBox=false; break; ----> ---- var checkBox=StoreConnector<TodoState, CheckBoxViewModel>(Converter: (store) { return CheckBoxViewModel(store,index); }, builder: (context, model) { return Offstage(child:Checkbox( value: model.done, onChanged: model.onClick) , offstage: ! model.store.state.showBox,) ; }); ---->[define CheckBox model]---- class CheckBoxViewModel {final Store Store; final int index; void Function(bool check) onClick; CheckBoxViewModel(this.store,this.index) { onClick = (check) { store.dispatch(Acts.check); store.state.todos[index].done = check; }; } get done{ return store.state.todos[index].done; }}Copy the code

This makes it easy to make changes to the state that would otherwise be difficult to do directly in TodoList.


conclusion

This is the end of this article. If you want to taste Flutter quickly, Flutter For Seven days is a must-have. If you want to explore it, follow in my footsteps and complete a Flutter tour. In addition, I have a Flutter wechat communication group. You are welcome to join and discuss Flutter issues together. My wechat account is ZDL1994328.