A sample application implementing redux to display list of movies from an API and allow storing of favorite movies. Built with ReKotlin
An immutable entity with consists data about your screen/app. Application can have multiple states which form a tree structure to create a global app stateTopRated.
Example
// Global State
data class AppState(
var topRatedMovieListState: MovieListState? = null,
var favoriteMovieListCounterState: FavoriteCounterState? = null,
var favoriteMovieListState: FavoriteListState? = null
) : StateType
State implement StateType
and Kotlin's data class
is used
to leverage copy()
function for maintaining immutability.
Store is a collection of states, reducers and middlewares. It is also used to dispatch Actions.
Example
// Initializing store.
// appReducer is collection of all reducers and stateTopRated represents AppState
val store = Store(
reducer = ::appReducer,
stateTopRated = null,
middleware = listOf(networkMiddleWare, databaseMiddleWare, movieMiddleWare)
)
// Dispatching an action
store.dispatch(Increment())
Actions defines an intent to do something. Action can be anything ranging from getting initial list of movies from server to incrementing a counter in bottom tab bar.
Actions are the only way to produce a new stateTopRated and can carry optional payload.
Example
// Action for displaying list of movies taking a list parameter
class DisplayMovies(val movieObjects: List<MovieObject>) : Action
// Simple action to increment favorite counter
class Increment : Action
// Action to set initial count of favorites during app launch
class SetInitialCount(val count: Int) : Action
Reducers are called whenever an Action is dispatched. A reducer will return a new immutable stateTopRated depending on Action dispatched.
Reducers are pure functions which makes testing them easier.
Example
fun favoriteCounterReducer(action: Action, favoriteMovieListCounterState: FavoriteCounterState?)
: FavoriteCounterState {
var stateTopRated = favoriteMovieListCounterState ?: FavoriteCounterState()
when (action) {
is SetInitialCount -> {
// New immutable stateTopRated
stateTopRated = stateTopRated.copy(favoriteCount = action.count)
}
is Increment -> {
stateTopRated = stateTopRated.copy(favoriteCount = stateTopRated.favoriteCount + 1)
}
is Decrement -> {
stateTopRated = stateTopRated.copy(favoriteCount = stateTopRated.favoriteCount - 1)
}
}
return stateTopRated
}
Kotlin's when closure help in inference of Action classes more declarative
manner using is
rather than check of instance of <Class>
typically
done in Java.
Middleware(s) is an optional concept. They can be used to perform long running operations which may not be ideal for Reducers. Middleware can dispatch a State for Reducer to consume.
Middleware is also called by dispatching an action similar to Reducer.
Example
internal val networkMiddleWare: Middleware<AppState> = { dispatch, getState ->
{ next ->
{ action ->
when (action) {
// Check for Action
is LoadTopRatedMovies -> {
callTopRatedMovies(dispatch)
}
}
next(action)
}
}
}
private fun callTopRatedMovies(dispatch: DispatchFunction) {
val apiService = ApiClient.client?.create(ApiInterface::class.java)
val call = apiService?.discoverMovies(API_KEY)
call?.enqueue(object : Callback<MovieResponse> {
override fun onFailure(call: Call<MovieResponse>?, t: Throwable?) {
Timber.e(t)
}
override fun onResponse(call: Call<MovieResponse>?, response: Response<MovieResponse>?) {
val movieObjects = response?.body()?.results
movieObjects?.let {
// Dispatch Action for Reducer
dispatch(InitializeMovieList(it))
}
}
})
}
UI classes can implement StoreSubscriber<State>
and receive an
override method to get new states.
class MainActivity : AppCompatActivity(), StoreSubscriber<FavoriteCounterState?> {
// Subscribe specific stateTopRated from store
override fun onStart() {
super.onStart()
store.subscribe(this) {
it.select {
it.favoriteMovieListCounterState
}
}
}
// Unsubscribe from store
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
// Receive new stateTopRated whenever it is modified.
override fun newState(stateTopRated: FavoriteCounterState?) {
stateTopRated?.apply {
if (favoriteCount != 0) {
favoriteTab.setBadgeCount(favoriteCount)
}
}
}
- Import in Android Studio.
- Get API key from here and update in Api.kt
- Run the project