You signed in with another tab or window.Reload to refresh your session.You signed out in another tab or window.Reload to refresh your session.You switched accounts on another tab or window.Reload to refresh your session.Dismiss alert
Mini is a minimal Flux architecture written in Kotlin that also adds a mix of useful features to build UIs fast.
Purpose
You should use this library if you aim to develop a reactive application with good performance (no reflection using code-gen).Feature development using Mini 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.
How to Use
Dispatcher
TheDispatcher is the hub that manages all data flow in a Flux application. It is basically a holder of store callbacks: each store registers itself and provides a callback for an action.
One important thing is that the dispatching is always performed in the same thread to avoid possible side-effects.
We can dispatch actions in the following ways:
// Dispatch an action on the main thread synchronouslydispatcher.dispatch(LoginAction(username="user", password="123"))// Post an event that will dispatch the action on the UI thread and return immediately.dispatcher.dispatchAsync(LoginAction(username="user", password="123"))
Store
TheStores 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 a plain object (usually adata class) that holds all information needed to display the view. States should always be inmutable. State classes should avoid using framework elements (View, Camera, Cursor...) in order to facilitate testing.
Stores subscribe to actions to change the application state after a dispatch. Mini generates the code that links dispatcher actions and stores using the@Reducer annotation over anon-private function that receives an Action as parameter.
AnAction is a simple class that usually represents a use case. It can also contain a payload that includes data to perform said action. When an action is triggered, it will be delivered via dispatcher to the stores that are going to do something with the action to change their state.
For example, we may want to log in to a service. We would create an action like this one:
data classLoginAction(valusername:String,valpassword:String)
When we receive the response from the server, we'll dispatch another action with the result:
data classLoginCompleteAction(valloginTask:Task,valuser:User?)
Actions will usually be triggered from Views or Controllers.
View changes
EachStore exposes a customStoreCallback though the methodobserve or aFlowable if you want to make use of RxJava. Both of them emits changes produced on their states, allowing the view to listen reactive the state changes. Being able to update the UI according to the newStore state.
If you make use of the RxJava methods, you can make use of theSubscriptionTracker interface to keep track of theDisposables used on your activities and fragments.
Tasks
ATask is a basic object to represent an ongoing process. They should be used in the state of ourStore to represent ongoing processes that must be represented in the UI.
You can also useTypedTask to save metadata related the current task.
IMPORTANT: Do not use TypedTask to hold values that must survive multiple task executions. Save them as a variable in the state instead.
Example
Given the example Stores and Actions explained before, the workflow will be:
View dispatchesLoginAction.
Store changes hisLoginTask 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 whenLoginTask is in running state.
The async call ends andLoginCompleteAction 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 fromLoginCompleteAction.
The View redirect to the HomeActivity if the task was success or shows an error if not.
Rx Utils
Mini includes some utility extensions over RxJava 2.0 to make easier listen state changes over theStores.
mapNotNull: Will emit only not null values over the givenmap clause.
select: LikemapNotNull but avoiding repeated values.
onNextTerminalState: Used to map aTask inside an state and listen the next terminal state(Success - Error). Executing a different closure depending of the result of the task.
Navigation
To avoid loops over when working with navigation based on a process result. You will need to make use ofonNextTerminalState after dispatch andAction that starts a process which result could navigate to a different screen.For example:
If we continually listen the changes of aTask and we navigate to a specific screen when theTask becomes successful. The state will stay on SUCCESS and if we navigate back to the last screen we will be redirected again.
Logging
Mini includes a customLoggerInterceptor to log any change in yourStore states produced from anAction. This will allow you to keep track of your actions, changes and side-effects more easily.To add the LoggerInterceptor to your application you just need to add a single instance of it to yourDispatcher after initialize it in yourApplication class or dependency injection code.
val loggerInterceptor=CustomLoggerInterceptor(stores().values)dispatcher.addInterceptor(loggerInterceptor)
Testing with Mini
Mini includes an extra library called mini-android-testing with a few methods andExpresso TestRules to simplify your UI tests over this architecture.
TestDispatcherRule : This rule will intercept any action that arrives to the Dispatcher, avoiding any call to the Store and their controllers. If we include this rule we will need to change the states manually in our tests.
CleanStateRule : It just reset the state of your stores before and after each test.
Example of test checking that an action is correctly dispatched:
You'll need to add the following snippet to yourApplication'sonCreate method. If you don't have it, then create it and reference it in yourAndroidManifest.xml file:
val stores= listOf<Store<*>>()// Here you'll set-up you store list, you can retrieve it using your preferred DI frameworkval dispatcher=MiniGen.newDispatcher()// Create a new dispatcher// Initialize MinistoreSubscriptions=MiniGen.subscribe(dispatcher, stores)stores.forEach { store-> store.initialize()}// Optional: add logging interceptor to log action eventsdispatcher.addInterceptor(LoggerInterceptor(stores)) { tag, msg->Log.d(tag, msg)}
License
Copyright 2019 BQ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.