A Dart app architecture library with uni-directional data flow inspired by RefluxJS and Facebook's Flux.
w_flux implements a uni-directional data flow pattern comprised of Actions, Stores, and FluxComponents.
Actionsinitiate mutation of app data that resides inStores.- Data mutations within
Storestrigger re-rendering of app view (defined inFluxComponents). FluxComponentsdispatchActionsin response to user interaction.- and the cycle continues...
An Action is a command that can be dispatched (with an optional data payload) and listened to.
In w_flux, Actions are the sole driver of application state change. FluxComponents dispatch Actions in response to
user interaction with the rendered view. Stores listen for these Action dispatches and mutate their internal data in
response, taking the Action payload into account as appropriate.
import 'package:w_flux/w_flux.dart';
// define an action
final Action<String> displayString = new Action<String>();
// dispatch the action with a payload
displayString('somePayload');
// listen for action dispatches
displayString.listen(_displayAlert);
_displayAlert(String payload) {
print(payload);
}BONUS: Actions are await-able!
They return a Future that completes after all registered Action listeners complete. It's NOT generally recommended to
use this feature within normal app code, but it is quite useful in unit test code.
A Store is a repository and manager of app state. The base Store class provided by w_flux should be extended to fit
the needs of your app and its data. App state may be spread across many independent stores depending on the complexity
of the app and your desired app architecture.
By convention, a Store's internal data cannot be mutated directly. Instead, Store data is mutated internally in
response to Action dispatches. Stores should otherwise be considered read-only, publicly exposing relevant data ONLY
via getter methods. This limited data access ensures that the integrity of the uni-directional data flow is maintained.
A Store can be listened to to receive external notification of its data mutations. Whenever the data within a Store
is mutated, the trigger method is used to notify any registered listeners that updated data is available. In w_flux,
FluxComponents listen to Stores, typically triggering re-rendering of UI elements based on the updated Store data.
import 'package:w_flux/w_flux.dart';
class RandomColorStore extends Store {
// Public data is only available via getter method
String _backgroundColor = 'gray';
String get backgroundColor => _backgroundColor;
// Actions relevant to the store are passed in during instantiation
RandomColorActions _actions;
RandomColorStore(RandomColorActions this._actions) {
// listen for relevant action dispatches
_actions.changeBackgroundColor.listen(_changeBackgroundColor);
}
_changeBackgroundColor(_) {
// action dispatches trigger internal data mutations
_backgroundColor = '#' + (new Random().nextDouble() * 16777215).floor().toRadixString(16);
// trigger to notify external listeners that new data is available
trigger();
}
}BONUS: Stores can be initialized with a stream transformer to modify the standard behavior of the trigger stream.
This can be useful for throttling UI rendering in response to high frequency Store mutations.
import 'package:rate_limit/rate_limit.dart';
import 'package:w_flux/w_flux.dart';
class ThrottledStore extends Store {
...
ThrottledStore(this._actions) : super.withTransformer(new Throttler(const Duration(milliseconds: 30))) {
...
}
}BONUS: Stores provide an optional terse syntax for action -> data mutation -> trigger operations.
// verbose syntax
actions.incrementCounter.listen(_handleAction);
_handleAction(payload) {
// perform data mutation
counter += payload;
trigger();
}
// equivalent terse syntax
triggerOnAction(actions.incrementCounter, (payload) => counter += payload);FluxComponents define the (optional) user interface for a w_flux unit and are responsible for rendering app view based
on 'Store' data as needed. FluxComponents listen to Stores and selectively re-render in response to their trigger
dispatches. FluxComponents retrieve relevant app data from these Stores via the exposed getter methods, and as such,
are internally stateless.
If user interaction with a FluxComponent is intended to mutate app state, this is accomplished by dispatching an
Action (with optional data payload). FluxComponents DO NOT mutate app state within Stores directly.
FluxComponent is an extension of Component
(as provided by react-dart) that reduces the amount of boilerplate needed to
operate within the w_flux architecture. The base FluxComponent class provided by w_flux should be extended to fit
the needs of your app.
By default, FluxComponents provide standard getters for the Actions and Store that they are initialized with.
They also automatically subscribe to the provided Store and re-render in response to its triggers.
import 'package:react/react.dart' as react;
import 'package:w_flux/w_flux.dart';
var RandomColorComponent = react.registerComponent(() => new _RandomColorComponent());
class _RandomColorComponent extends FluxComponent<RandomColorActions, RandomColorStore> {
render() {
return react.div({
// accesses the backgroundColor via the store's public getter
'style': {'padding': '50px', 'backgroundColor': store.backgroundColor, 'color': 'white'}
}, [
'This module uses a flux pattern to change its background color.',
react.button({
'style': {'padding': '10px', 'margin': '10px'},
// triggers a change of background color by dispatching an action on button click
'onClick': actions.changeBackgroundColor
}, 'Change Background Color')
]);
}
}BONUS: Optional overrides are available for more granular control of FluxComponent rendering.
If the FluxComponent's Store is actually a complex object containing multiple Stores (each trigger independently),
the component's redrawOn list can be overridden to confine re-rendering to trigger dispatches that originate from
specific sub-stores.
The FluxComponent's getStoreHandlers method can be used to register more fine grained Store trigger handling if necessary.
import 'package:react/react.dart' as react;
import 'package:w_flux/w_flux.dart';
class ComplexStore {
ThisStore thisOne = new ThisStore();
ThatStore thatOne = new ThatStore();
OtherStore otherOne = new OtherStore();
}
var ComplexComponent = react.registerComponent(() => new _ComplexComponent());
class _ComplexComponent extends FluxComponent<ComplexActions, ComplexStore> {
// re-render will automatically be initiated in response to triggers from these two stores
// (e.g. no rendering will occur on store.otherOne triggers)
redrawOn() => [store.thisOne, store.thatOne];
// whenever store.otherOne triggers, the _handleOtherTrigger method will be executed
// (no rendering is triggered)
getStoreHandlers() => {store.otherOne: _handleOtherTrigger};
_handleOtherTrigger(otherStore) {
// decide whether to re-render based on some criteria
if (otherStore.isReady) {
// manually initiate re-render of this component
redraw();
}
}
render() {
...
}
}If you are using the over_react package
to build UI components, they offer a strongly-typed version of the "flux
component" pattern shown above.
https://github.com/workiva/over_react#flux-component-boilerplate
Simple examples of w_flux usage can be found in the example directory. The example README
includes instructions for building / running them.
w_flux implements a uni-directional data flow within an isolated application or code module. If w_flux is used as the
internal architecture of a library, this internal data flow should be considered when defining the external API.
- External API methods intended to mutate internal state should dispatch
Actions, just like any internal user interaction. - External API methods intended to query internal state should leverage the existing read-only
Storegetter methods. - External API streams intended to notify the consumer about internal state changes should be dispatched from the
internal
Stores, similar to theirtriggers. - Factory constructors for useful 'root'
FluxComponentscan be exposed publicly for use in external react-dart based rendering hierarchies. These react components can be internally initialized with theActionsandStoresneeded for normal operation without inadvertently exposing them externally.
w_module is a Dart library that defines a standard code module API that can be
used seamlessly with w_flux internals to satisfy the above recommendations (complete with examples).
This project leverages the dart_dev package for most of its tooling needs, including static analysis, code formatting, running tests, collecting coverage, and serving examples. Check out the dart_dev readme for more information.
