here’s the github link: https://github.com/lzyy/bloc_redux

After using flutter for some time, it seems there are no the go-to way to handle state and manage data flow. well, there are, one is flutter_redux, and the other is flutter_bloc. Let’s talk flutter_redux first.

It’s kind of the official implementation of redux. mainly combined with two parts: StoreProvider and StoreConnector . the former used to hold Store , the latter used to react to new state.

// Every time the button is tapped, an action is dispatched and

// run through the reducer. After the reducer updates the state,

// the Widget will be automatically rebuilt with the latest

// count. No need to manually manage subscriptions or Streams!

new StoreConnector<int, String>(

converter: (store) => store.state.toString(),

builder: (context, count) {

return new Text(

count,

style: Theme.of(context).textTheme.display1,

);

},

)

the problem of StoreConnector is whenever state updated, it will be triggered. even the state contains 10 properties, and only 1 bool property is updated.

// Creates the base [NextDispatcher].

//

// The base NextDispatcher will be called after all other middleware provided

// by the user have been run. Its job is simple: Run the current state through

// the reducer, save the result, and notify any subscribers.

NextDispatcher _createReduceAndNotify(bool distinct) {

return (dynamic action) {

final state = reducer(_state, action); if (distinct && state == _state) return; _state = state;

_changeController.add(state);

};

}

that’s the redux notify mechanism. as long as the state are not equal, _changeController is notified. in react-redux , there’s some optimization, using connect 's mapStateToProps , the component will be rerender only when the props it care updated. but flutter_redux can’t do this.

now let’s dive into flutter_bloc , before talk about it, let’s talk about bloc, which is promoted by google fellows.

it’s more of an idea rather than spec or protocol. flutter_bloc is an implementation of bloc. after looking into the code, it seems not quite different than redux, but added some concepts like BlocSupervisor BlocDelegate Transaction , make it more complex. state handling is no different than redux, even worse, without == check.

void _bindStateSubject() {

Event currentEvent; (transform(_eventSubject) as Observable<Event>).concatMap((Event event) {

currentEvent = event;

return mapEventToState(_stateSubject.value, event);

}).forEach(

(State nextState) {

final transition = Transition(

currentState: _stateSubject.value,

event: currentEvent,

nextState: nextState,

);

BlocSupervisor().delegate?.onTransition(transition);

onTransition(transition);

_stateSubject.add(nextState);

},

);

}

nextState is directly add to _stateSubject without checking equal. meanwhile author seems to encourage using multi blocs which is not handy to use. (which bloc should i dispatch to?)

return BlocBuilder<LoginEvent, LoginState>(

bloc: widget.loginBloc,

builder: (

BuildContext context,

LoginState loginState,

) {

if (_loginSucceeded(loginState)) {

widget.authBloc.dispatch(Login(token: loginState.token));

widget.loginBloc.dispatch(LoggedIn());

}

}

);

So, these two libraries are not suited for my needs, let’s make another wheel!

Bloc_Redux

the problem with flutter_redux is its state handling. reducer return a new state, and it’s hard to separate modified properties, even it can, there should be a map to hold these relationships, too complicated.

Flutter already provides StreamBuilder , let widgets listen the streams they like, when an action dispatched, change these streams content. that’s it.

the reducer’s role is replaced by bloc.

/// Action

///

/// every action should extends this class

abstract class BRAction<T> {

T payload;

} /// State

///

/// Input are used to change state.

/// usually filled with StreamController / BehaviorSubject.

/// handled by blocs.

///

/// implements disposable because stream controllers needs to be disposed.

/// they will be called within store's dispose method.

abstract class BRStateInput implements Disposable {} /// Output are streams.

/// followed by input. like someController.stream

/// UI will use it as data source.

abstract class BRStateOutput {} /// State

///

/// Combine these two into one.

abstract class BRState<T extends BRStateInput, U extends BRStateOutput> {

T input;

U output;

} /// Bloc

///

/// like reducers in redux, but don't return a new state.

/// when they found something needs to change, just update state's input

/// then state's output will change accordingly.

typedef Bloc<T extends BRStateInput> = void Function(BRAction action, T input); /// Store

///

/// widget use `store.dispatch` to send action

/// store will iterate all blocs to handle this action

///

/// if this is an async action, blocs can dispatch another action

/// after data has received from remote.

abstract class BRStore<T extends BRStateInput, U extends BRState>

implements Disposable {

List<Bloc<T>> blocs;

U state; void dispatch(BRAction action) {

blocs.forEach((f) => f(action, state.input));

} dispose() {

state.input.dispose();

}

}

that’s the core code. State is separated into StateInput and StateOutput , StateInput is handled by bloc, StateOutput is consumed by widgets to receive latest data.

Store has a dispose method, because it will be added to StoreProvider , when it disposed, the streams can be closed safely. Let’s see a demo

/// Actions

class ColorActionSelect extends BRAction<Color> {} /// State

class ColorStateInput extends BRStateInput {

final BehaviorSubject<Color> selectedColor = BehaviorSubject();

final BehaviorSubject<List<ColorModel>> colors = BehaviorSubject(); dispose() {

selectedColor.close();

colors.close();

}

} class ColorStateOutput extends BRStateOutput {

StreamWithInitialData<Color> selectedColor;

StreamWithInitialData<List<ColorModel>> colors; ColorStateOutput(ColorStateInput input) {

selectedColor = StreamWithInitialData(

input.selectedColor.stream, input.selectedColor.value);

colors = StreamWithInitialData(input.colors.stream, input.colors.value);

}

} class ColorState extends BRState<ColorStateInput, ColorStateOutput> {

ColorState() {

input = ColorStateInput();

output = ColorStateOutput(input);

}

} /// Blocs

Bloc<ColorStateInput> colorSelectHandler = (action, input) {

if (action is ColorActionSelect) {

input.selectedColor.add(action.payload);

var colors = input.colors.value

.map((colorModel) => colorModel

..isSelected = colorModel.color.value == action.payload.value)

.toList();

input.colors.add(colors);

}

}; /// Store

class ColorStore extends BRStore<ColorStateInput, ColorState> {

ColorStore() {

state = ColorState();

blocs = [colorSelectHandler]; // init

var _colors = List<ColorModel>.generate(

30, (int index) => ColorModel(RandomColor(index).randomColor()));

_colors[0].isSelected = true;

state.input.colors.add(_colors);

state.input.selectedColor.add(_colors[0].color);

}

}

Store is like brain, it receives actions and make blocs handle these actions to change streams accordingly. widgets will receive streams’ latest data.



@override

Widget build(BuildContext context) {

final store = StoreProvider.of<ColorStore>(context);

final colors = store.state.output.colors; class ColorsWidget extends StatelessWidget {Widget build(BuildContext context) {final store = StoreProvider.of (context);final colors = store.state.output.colors; return StreamBuilder<List<ColorModel>>(

stream: colors.stream,

initialData: colors.initialData,

builder: (context, snapshot) {

final colors = snapshot.data;

return SliverGrid.count(

crossAxisCount: 6,

children: colors.map((colorModel) {

return GestureDetector(

onTap: () {

store.dispatch(

ColorActionSelect()..payload = colorModel.color);

},

child: Container(

decoration: BoxDecoration(

color: colorModel.color,

border: Border.all(width: colorModel.isSelected ? 4 : 0)),

),

);

}).toList());

},

);

}

}

StreamBuilder is used to receive StateOutput stream’s value, and use store.dispatch to send action(one page one store). It’s that simple.

Source: https://github.com/lzyy/bloc_redux