Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Redux for Android. Predictable state container library for Java/Android

License

NotificationsYou must be signed in to change notification settings

Yarikx/reductor

Repository files navigation

DownloadAndroid ArsenalBuild StatusCodecov

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.

Reductor advantages:

  • 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

Blog posts

Installation

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

Getting started

First example: Simple counter

//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}

API

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:

  • Callstore.getState() to get the stateStore holds at the moment
  • Callstore.subscribe(state -> doSomething(state)). Calling subscribe will notify provided listenerevery time state changes

And only one way how to change the state:

  • Callstore.dispatch(action) to deliver and process it by correspondingReducer.

Advanced use

Combine Reducers

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.

AutoReducer

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 castingaction.value to 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.

Defining action creators

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.

Roadmap

  • 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

Stars

Watchers

Forks

Packages

No packages published

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp