Flurt is a minimal Flux architecture written in Dart. I'm not a Dart developer, this is an implementation of one of my android team architectures ported to Dart. The library could be not 100% stable and the implementation could be not the best one.
You should use this library if you aim to develop a reactive application with good performance (no reflection using code-gen). Feature development using Flurt is fast compared to traditional architectures (like CLEAN or MVP), low boilerplate and state based models make feature integration and bugfixing easy as well as removing several families of problems like concurrency or view consistency across screens.
Actions are helpers that pass data to the Dispatcher. They represent use cases of our application and are the start point of any state change made during the application lifetime.
class LoginAction extends Action {
String username;
String password;
LoginAction(String username, String password) {
this.username = username;
this.password = password;
}
}
The dispatcher receives Actions and broadcast payloads to registered callbacks. The instance of the Dispatcher must be unique across the whole application and it will execute all the logic in the main thread making state mutations synchronous.
dispatcher.dispatch(LoginAction("user","123"));
The Stores are holders for application state and state mutation logic. In order to do so they expose pure reducer functions that are later invoked by the dispatcher.
The state is plain object (usually a data class) that holds all information needed to display the view. State should always be immutable. State classes should avoid using framework elements (View, Camera, Cursor...) in order to facilitate testing.
Stores subscribe to actions though the Dispatcher
to change the application state after a dispatch.
class SessionState {
String loggedUsername;
SessionState({String loggedUsername = null}) {
this.loggedUsername = loggedUsername;
}
}
class SessionStore extends Store<SessionState> {
Dispatcher dispatcher;
SessionStore(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
@override
SessionState initialState() => SessionState();
@override
void init() {
dispatcher.subscribe(LoginAction, (LoginAction action) {
state = SessionState(loggedUsername: action.username)
});
}
}
Each Store
exposes an Observable
making use of RxJava. It emits changes produced on the store state, allowing the view to listen reactive the state changes. Being able to update the UI according to the new Store
state.
store
.asObservable()
.map((state) => state.loggedUsername)
.listen((loggedUser) => toGoHome());
You can make use of the SubscriptionTracker
class to keep track of the StreamSubscription
used on your views.
A Task is a basic object to represent an ongoing process. They should be used in the state of our Store
to represent ongoing processes that must be represented in the UI.
Using a task an example workflow will be:
- View dispatch
LoginAction
. - Store changes his
LoginTask
status to running and call though his SessionController which will do all the async work to log in the given user. - View shows an Spinner when
LoginTask
is in running state. - The async call ends and
LoginCompleteAction
is dispatched on UI, sending a nullUser
and an error stateTask
if the async work failed or a successTask
and anUser
. - The Store changes his state to the given values from
LoginCompleteAction
. - The View redirect to the HomeActivity if the task was success or shows an error if not.