ReduxKotlin

ReduxKotlin

  • Getting Started
  • API
  • FAQ
  • Github
  • Need help?

›Advanced Tutorial

Introduction

  • Getting Started with Redux
  • Motivation
  • Core Concepts
  • Three Principles
  • Threading
  • Learning Resources
  • Ecosystem
  • Examples

Basic Tutorial

  • Basic Tutorial: Intro
  • Actions
  • Reducers
  • Store
  • Data flow

Advanced Tutorial

  • Advanced Tutorial: Intro
  • Async Actions
  • Middleware

FAQ

  • FAQ Index
  • General
  • Reducers
  • Store Setup
  • Multiplatform

Other

  • Glossary
  • Troubleshooting
  • Feedback

API Reference

  • API Reference
  • createStore
  • createThreadSafeStore
  • createSameThreadEnforcedStore
  • Store
  • applyMiddleware
  • compose

Async Actions

In the basics guide, we built a simple todo application. It was fully synchronous. Every time an action was dispatched, the state was updated immediately.

In this guide, we will build a different, asynchronous application. It will use the Cat API to fetch a list of Cat breeds. How does asynchronicity fit into Redux flow?

Actions

When you call an asynchronous API, there are two crucial moments in time: the moment you start the call, and the moment when you receive an answer (or a timeout).

Each of these two moments usually require a change in the application state; to do that, you need to dispatch normal actions that will be processed by reducers synchronously. Usually, for any API request you'll want to dispatch at least three different kinds of actions:

  • An action informing the reducers that the request began.

    The reducers may handle this action by toggling an isFetching flag in the state. This way the UI knows it's time to show a spinner.

  • An action informing the reducers that the request finished successfully.

    The reducers may handle this action by merging the new data into the state they manage and resetting isFetching. The UI would hide the spinner, and display the fetched data.

  • An action informing the reducers that the request failed.

    The reducers may handle this action by resetting isFetching. Additionally, some reducers may want to store the error message so the UI can display it.

You may use a separate actions for every step of a request:

class Actions {
    object FetchingItemsStarted
    data class FetchingItemsSuccessAction(val itemsHolder: ItemsHolder)
    data class FetchingItemsFailedAction(val message: String)
}

Choosing how to represent the flow with actions is up to you and your team. You can have a single set of ApiActions that include an endpoint, status, and result in one action object, for example.

Whatever convention you choose, stick with it throughout the application.
We'll use separate types in this tutorial.

Async Actions

The standard way to do async actions in Redux is to use the Redux-Kotlin-Thunk middleware. It comes in a separate package called redux-kotlin-thunk. We'll explain how middleware works in general later; for now, there is just one important thing you need to know: by using this specific middleware, you can dispatch a function and it will be executed by the thunk-middleware. These functions are known as thunks. Thunks don't need to be pure; it is thus allowed to have side effects, including executing asynchronous API class. The function can also dispatch actions--like normal synchronous actions.

Thunks can be defined anywhere, but it useful to group them together logically. For example all thunks for an API can be grouped together in a class. Or they can be top level functions in a file. It is up to you and your team.

NetworkThunks from the Name Game example app shows how API request can be completed asynchronously using coroutines.

NetworkThunks.kt

class NetworkThunks(networkContext: CoroutineContext) {
    private val networkScope = CoroutineScope(networkContext)

    private fun repoForCategory(categoryId: QuestionCategoryId) = when (categoryId) {
        QuestionCategoryId.CATS -> CatItemRepository()
        QuestionCategoryId.DOGS -> DogItemRepository()
    }

    fun fetchItems(categoryId: QuestionCategoryId, numQuestions: Int) = thunk { dispatch, getState, extraArgument ->
        val repo = repoForCategory(categoryId)
        Logger.d("Fetching StoreInfo and Feed")
        networkScope.launch {
            dispatch(Actions.FetchingItemsStartedAction())
            val result = repo.fetchItems(numQuestions)
            if (result.isSuccessful) {
                Logger.d("Success")
                dispatch(Actions.FetchingItemsSuccessAction(result.response!!))
            } else {
                Logger.d("Failure")
                dispatch(Actions.FetchingItemsFailedAction(result.message!!))
            }
        }
    }
}

/**
 * Convenience function so state type does is not needed every time a thunk is created.
 */
fun thunk(thunkLambda: (dispatch: Dispatcher, getState: GetState<AppState>, extraArgument: Any?) -> Any) =
        createThunk(thunkLambda)

Note on Networking

There are several options for networking. In this example it is abstracted over, and the implementation is with Ktor, a multiplatform networking library. In a strictly Android app this could be Retrofit & OkHttp.

Connecting to UI

Dispatching async actions is no different from dispatching synchronous actions, so we won't discuss this in detail. See the Example: Name Game for the complete source code discussed in this example.

Next Steps

Read Async Flow to recap how async actions fit into the Redux flow.

← Advanced Tutorial: IntroMiddleware →
  • Actions
  • Connecting to UI
  • Next Steps
ReduxKotlin
Docs
Getting StartedCore ConceptsBasicsAdvanced
Community
#redux slackTrello board
More
GitHubStar
Thank you to Dan Abramov and the Redux.js.org documentation authors from which this was forked.
Some icons copyright Font Awesome and Noun Project (Hassan ali, ProSymbols)