In formal introductionReduxWhy do we need state management? If this is clear to you, it is advisable to skip this section. If our application is simple enough,FlutterAs a declarative framework, you might just need to map data to views. You probably don’t need state management, as follows.But as you add functionality, your application will have dozens or even hundreds of states. This is what your application should look like.What the hell is this? It’s hard to test and maintain our state clearly because it seems so complicated! There are also multiple pages that share the same status. For example, when you enter a “like” post and exit to the external thumbnail display, the external thumbnail display also needs to show the number of likes, so you need to synchronize the two states.FlutterIt actually gives us a way to manage our state in the first place, which isStatefulWidget. But we soon found out that it was the culprit in the first place. inStateBelonging to a particularWidgetIn the multipleWidgetWhile you can usecallbackSolution, but when the nesting is deep enough, we add an awful lot of junk code. At this point, we urgently needed a framework to help us clarify these relationships, and a state management framework came into being.

Add the story/flutter_redux

First add it in YAML

Story: ^ 4.0.0 + 3 flutter_redux: ^ 0.6.0Copy the code

For details about the latest version, see: pub.flutter-io.cn/flutter_red… / redux

Then run

flutter pub get
Copy the code

Obtain the latest package to the local, in the required folder to import

import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
Copy the code

example

We also use the example of clicking a button to add a number

The first thing you need to do is wrap StoreProvider around the parts you want to display, like this:

 @override
  Widget build(BuildContext context) {
    _build += 'p1 build \n';
    return StoreProvider(
      store: store,
      child: Scaffold(
          appBar: AppBar(
            title: Text('ScopedModel'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(_build),
                StoreConnector<int.String>(
                  converter: (store) => store.state.toString(),
                  builder: (context, count) {
                    _build += 's1';
                    return Text('$count');
                  },
                ),
                StoreConnector<int, VoidCallback>(
                  converter: (store) {
                    return () => store.dispatch(Actions.Increment);
                  },
                  builder: (context, callback) {
                    return OutlineButton(
                      child: Text('+'),
                      onPressed: callback,
                    );
                  },
                ),
                StoreConnector<int, VoidCallback>(
                  converter: (store) {
                    return () => store.dispatch(Actions.Decrement);
                  },
                  builder: (context, callback) {
                    return OutlineButton(
                      child: Text(The '-'),
                      onPressed: callback,
                    );
                  },
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    OutlineButton(
                      child: Icon(Icons.refresh),
                      onPressed: () {
                        if (mounted) setState(() {});
                      },
                    ),
                    OutlineButton(
                      child: Icon(Icons.clear),
                      onPressed: () {
                        if (mounted)
                          setState(() {
                            _build = ' '; }); }, [,], [, (), (), (), (); }Copy the code

Then define the click event handler function, which uses enum to distinguish between different events. By default, it does not handle events.

enum Actions { Increment, Decrement }
int counterReducer(int state, dynamic action) {
  if (action == Actions.Increment) {
    return state + 1;
  } else if (action == Actions.Decrement) {
    return state - 1;
  }
  return state;
}
Copy the code

Store is an object that needs to be defined before it can be used

final store = Store(counterReducer, initialState: 0);
Copy the code

It looks like this when it’s displayed

StoreConnector<int.String>(
      converter: (store) => store.state.toString(),
      builder: (context, count) {
        _build += 's1';
        return Text('$count'); },)Copy the code

Of course a page can display multiple data can use store

to display different data. We display two pieces of data and refresh them separately without affecting each other.

Model is defined as follows:

class _Model {
  int value, count;
  _Model({this.value, this.count}) { value ?? =0; count ?? =0; }}Copy the code

Actions and functions are defined as follows:


_Model counterReducer(_Model state, dynamic action) {
  if (action == Actions.IncrementValue) {
    state.value += 1;
    return state;
  } else if (action == Actions.DecrementCount) {
    state.count -= 1;
    return state;
  } else if (action == Actions.IncrementCount) {
    state.count += 1;
  } else if (action == Actions.DecrementValue) {
    state.value -= 1;
  }
  return state;
}

enum Actions { IncrementValue, IncrementCount, DecrementCount, DecrementValue }
Copy the code

The effect is as follows:

Here, P1 indicates that the entire page is refreshed, S1 indicates that the value is refreshed once, and S2 indicates that the page is refreshed once. Full code view

In fact, this is already a partial refresh of the page, and the performance has reached a high level.

The principle of

First we need to build our data storemodel

Model is stored in the Store. When we click the button, the event will be sent to the Store. Reducer, we decide which operation is in the function we wrote to accept the event and execute the response operation respectively. Take a look at the Store constructor:

 Store(
    this.reducer, {
    State initialState,
    List<Middleware<State>> middleware = const[].bool syncStream = false.bool distinct = false,
  }) : _changeController = StreamController.broadcast(sync: syncStream) {
    _state = initialState;
    _dispatchers = _createDispatchers(
      middleware,
      _createReduceAndNotify(distinct),
    );
  }
Copy the code

InitialState is the state that the model initializes and is of type T that we pass into Store

. The Reducer type is State Function(State State, dynamic Action), and action is a callback type, any type. Enumeration is recommended for convenience. Middleware contains an array of type

to pass events from the beginning of the array to the end.

Let’s add an example of Middleware:

 mw(Store<_Model> store, action, NextDispatcher next) {
    print('1:The ${new DateTime.now()}: $action');
    next(action);
  }

  mw2(Store<_Model> store, action, NextDispatcher next) {
    print('2:The ${new DateTime.now()}: $action');
    next(action);
  }
Copy the code

When I initialize it, I write it like this

  store = Store(counterReducer, initialState: _Model(), middleware: [
      mw,
      mw2,
    ]);
Copy the code

Bind data to the UI

First we use StoreConnector

or StoreBuilder

to display the content.

,t>

  StoreConnector<_Model, String>(
      converter: (store) => store.state.value.toString(),
      builder: (context, count) {
        return Text('value:$count'); },),Copy the code

So how do messages get sent to the Builder to refresh the UI?

StoreBuilder

is the encapsulated StoreConnector

, and StoreConnector

is the encapsulated _StoreStreamListener.
,t>
,t>

const _StoreStreamListener({
    Key key,
    @required this.builder,/// The constructor
    @required this.store,/// store
    @required this.converter,///The transition function
    this.distinct = false.this.onInit,/// Init to monitor
    this.onDispose,/// Destruction of listening
    this.rebuildOnChange = true./// By default, the UI is redrawn when the data changes
    this.ignoreChange,///Ignore the change
    this.onWillChange,/// Is going to change
    this.onDidChange,/// Has been changed
    this.onInitialBuild,/// The first frame callback
  }) 
Copy the code

_StoreStreamListener is an inherited StatefulWidget so you can listen for various functions. In didUpdateWidget function according to latestValue = Widget.converter (widget.store); If rebuildOnChange is true, a StreamBuilder is returned, and if false, a widget.builder(context,lastValue) is returned. When lastValue changes and other conditions are met, Send a stream to the StreamBuilder to refresh the UI.

Key code:

  @override
  Widget build(BuildContext context) {
    return widget.rebuildOnChange
        ? StreamBuilder<ViewModel>(
            stream: stream,
            builder: (context, snapshot) => widget.builder(
              context,
              latestValue,
            ),
          )
        : widget.builder(context, latestValue);
  }
Copy the code

How does Redux allow multiple functions to be called in sequence

[A,B,C] in order of A->B->C,

 dynamic dispatch(dynamic action) {
    return _dispatchers[0](action);
  }
Copy the code

Having initially inserted the broadcast _createReduceAndNotify into dispatch, Each dispatch(AC) calls _changecontroller.add (state), _createStream() in init in _StoreStreamListenerState initialization, and _changeCont in the former Roller has been listened on and finally executes _handleChange->sink.add(VM) to trigger _StoreStreamListenerState->StreamBuilder

().

/// Create a stream that sends the new state
NextDispatcher _createReduceAndNotify(bool distinct) {
  return (dynamic action) {
    final state = reducer(_state, action);
    if (distinct && state == _state) return;
    _state = state;
    _changeController.add(state);
  };
}

/// Create a stream that notifies the UI to refresh
  void _createStream() {
    stream = widget.store.onChange
        .where(_ignoreChange)
        .map(_mapConverter)
        .where(_whereDistinct)
        .transform(StreamTransformer.fromHandlers(handleData: _handleChange));
  }
/// The function that ultimately converts stream
  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);
      });
    }
/// Send the new value to the StreamBuilder
    sink.add(vm);
  }
Copy the code

Now that you have a good idea of how Redux works, look at the architecture diagram below.

The flow chart is as follows:

The final architecture diagram is as follows:

reference

  • Source making
  • This section provides examples of code
  • Nice loading animation

The article summary

Dart asynchrony and multithreading

Insight into state management –ScopeModel

Learn more about Flutter management –Redux

Detailed explanation of Flutter (iii. In-depth understanding of state management –Provider)

4. Deeper understanding of state management –BLoC

A detailed explanation of Flutter

1, Learn about the Stream

7. Understand the principle of drawing deeply

Detailed explanation of Flutter

Project recommend

  • A cool loading animation library
  • Flutter100 + component usage examples
  • Flutter entry to advanced ebook

The public,