- Notifications
You must be signed in to change notification settings - Fork27
Redux for Android. Predictable state container library for Java/Android
License
Yarikx/reductor
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Redux inspired predictable state container library for Java/Android.
Reductor can help you make your state mutations easier to read, write and reason about.It leverages annotation processing to validate correctness and generate boilerplate code at compile time, allowing you to express state reducers in a concise way as pure java functions.As Redux it's based on three principles (fromRedux documentation):
- Single source of truth
- State is read-only
- Changes are made with pure functions
Key point of this implementation was to keep the original concept of Redux to reuse most of existing approachesbut provide nice Java API and preserve types as much as possible.
- Lightweight
- Do not use reflection
- Follow implementation ofRedux
- Allow to compose a state with@CombinedState
- Allow to define Reducers in typesafe way with@AutoReducer
Note: This version is still under development, API may change till version 1.0
repositories { jcenter()}dependencies { compile'com.yheriatovych:reductor:x.x.x' apt'com.yheriatovych:reductor-processor:x.x.x'}Apt dependency onreductor-processor is only necessaryif you want to use features as@CombinedState and@AutoReducer
//state will be just integer//define actions@ActionCreatorinterfaceCounterActions {StringINCREMENT ="INCREMENT";StringADD ="ADD";@ActionCreator.Action(INCREMENT)Actionincrement();@ActionCreator.Action(ADD)Actionadd(intvalue);}//Define reducer@AutoReducerabstractclassCounterReducerimplementsReducer<Integer> {@AutoReducer.InitialStateintinitialState() {return0; }@AutoReducer.Action(value =CounterActions.INCREMENT,from =CounterActions.class)intincrement(intstate) {returnstate +1; }@AutoReducer.Action(value =CounterActions.ADD,from =CounterActions.class)intadd(intstate,intvalue) {returnstate +value; }publicstaticCounterReducercreate() {returnnewCounterReducerImpl();//Note: usage of generated class }}//Now you can create store and dispatch some actionspublicstaticvoidmain(String[]args) {//Now you can create store and dispatch some actionsStore<Integer>counterStore =Store.create(CounterReducer.create());//you can access state anytime with Store.getState()System.out.println(counterStore.getState());//print 0//no need to implement CounterActions, we can do it for youCounterActionsactions =Actions.from(CounterActions.class);//you can subscribe to state changescounterStore.subscribe(state ->System.out.println(state));counterStore.dispatch(actions.increment());//print 1counterStore.dispatch(actions.increment());//print 2counterStore.dispatch(actions.add(5));//print 7}
Main point of interaction with state isStore object.Store is actually container for your state.
There are two ways of accessing the state inside theStore:
- Call
store.getState()to get the stateStoreholds at the moment - Call
store.subscribe(state -> doSomething(state)). Calling subscribe will notify provided listenerevery time state changes
And only one way how to change the state:
- Call
store.dispatch(action)to deliver and process it by correspondingReducer.
For one store you need one Reducer, however usually state is complexand it's good practice to separate logic to separate it into multiple reducers.
You can do it manually by creating Reducer which delegate reducing logic to 'smaller' reducers
//Complex stateclassTodo {List<String>items;StringsearchQuery;publicTodo(List<String>items,StringsearchQuery) {this.items =items;this.searchQuery =searchQuery; } }//define reducer per sub-statesclassItemsReducerimplementsReducer<List<String>> {@OverridepublicList<String>reduce(List<String>strings,Actionaction) {...} }classQueryReducerimplementsReducer<String> {@OverridepublicStringreduce(Stringfilter,Actionaction) {...} }//define combined reducerclassTodoReducerimplementsReducer<Todo> {privateItemsReduceritemsReducer =newItemsReducer();privateQueryReducerqueryReducer =newQueryReducer();@OverridepublicTodoreduce(Todotodo,Actionaction) {//composing new state based on sub-reducersreturnnewTodo(itemsReducer.reduce(todo.items,action),queryReducer.reduce(todo.searchQuery,action) ); } }
This approach works but requires developer to write a bit of boilerplate of dispatching sub-states to sub-reducers.That's why Reductor can do boring work for you. Just use@CombinedState to generate correspondingReducer
//Complex state@CombinedStateinterfaceTodo {List<String>items();StringsearchQuery();}//define reducer per sub-statesclassItemsReducerimplementsReducer<List<String>> {@OverridepublicList<String>reduce(List<String>strings,Actionaction) {returnnull;}}classQueryReducerimplementsReducer<String> {@OverridepublicStringreduce(Stringfilter,Actionaction) {returnnull;}}publicstaticvoidmain(String[]args) {//Using generated TodoReducerReducer<Todo>todoReducer =TodoReducer.builder() .itemsReducer(newItemsReducer()) .searchQueryReducer(newQueryReducer()) .build();}
Note that@CombinedState annotated class needs to be interface orAutoValue abstract class.
Consider followingReducer which managesList<String>.
Note:PCollections library is used as implementation of persistent collections.
//classItemsReducerimplementsReducer<List<String>> {@OverridepublicList<String>reduce(List<String>items,Actionaction) {switch (action.type) {case"ADD_ITEM": {Stringvalue = (String)action.value;returnTreePVector.from(items) .plus(value); }case"REMOVE_ITEM": {Stringvalue = (String)action.value;returnTreePVector.from(items) .minus(value); }case"REMOVE_BY_INDEX": {intindex = (int)action.value;returnTreePVector.from(items) .minus(index); }default:returnitems; } }}
This way of writing reducers is canonical but have some disadvantages:
- Boilerplate code for switch statement
- Unsafe casting
action.valueto expected type
That's why Reductor has@AutoReducer to help developer by generatingreduce method
//AutoReducer annotated class should be abstract class which implement Reducer interface@AutoReducerabstractclassItemsReducerimplementsReducer<List<String>> {//Each 'handler' should be annotated with @AutoReducer.Action@AutoReducer.Action("ADD_ITEM")List<String>add(List<String>state,Stringvalue) {returnTreePVector.from(state) .plus(value); }@AutoReducer.Action("REMOVE_ITEM")List<String>remove(List<String>state,Stringvalue) {returnTreePVector.from(state) .minus(value); }@AutoReducer.Action("REMOVE_BY_INDEX")List<String>removeByIndex(List<String>state,intindex) {returnTreePVector.from(state) .minus(index); }staticItemsReducercreate() {//Note: ItemsReducerImpl is generated classreturnnewItemsReducerImpl(); }}
@AutoReducer.Action annotation supports declaring action creator.That will check at compile time if there is such action creator with the same parameters.Example:interface,usage.
In Reductor, an action is represented with classAction.It contains two fields:
- type: defines action id or name.
- values: arrays of arbitrary objects that can be added as payload.
You can create this Actions ad-hoc, just before dispatching.However usually it's more natural and readable way to encapsulate it into "Action creator" function, like:
ActionaddItemToCart(intitemId,Stringname,intprice) {returnAction.create("CART_ADD_ITEM",itemId,name,price);}
But that's not fun to write code for just bundling arguments inside.That's why Reductor lets you define all your action creators as just interface functions.
@ActionCreatorinterfaceCartActions{@ActionCreator.Action("CART_ADD_ITEM")ActionaddItem(intitemId,Stringname,intprice);@ActionCreator.Action("CART_REMOVE_ITEM")ActionremoveItem(intitemId);}
Reductor will generate implementation. To get the instance just callActions.from(class):
CartActionscartActions =Actions.from(CartActions.class);store.dispatch(cartActions.addItem(42,"Phone",350));
The information about actions structure is also used to check if@AutoReducerreducer actions have the same structure and name.
- Support Kotlin data classes to use with
@CombinedState - Better documentation
- Minimize usage of generated code from a source code
- Add more example:
- Async actions
- Time-traveling
- Dispatching custom actions with Middleware
- Using Rx with store
About
Redux for Android. Predictable state container library for Java/Android
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Contributors2
Uh oh!
There was an error while loading.Please reload this page.