Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Redux + (RxKotlin | RxSwift) = Awesome Native Mobile Apps —Part 3 — Reducer and More

DZone's Guide to

Redux + (RxKotlin | RxSwift) = Awesome Native Mobile Apps —Part 3 — Reducer and More

We continue our journey to improve the mobile developer experience with Redux by learning about Reducer and other features.

· Mobile Zone ·
Free Resource

These libraries can be a boon for native app developers. See how they change the developer experience in the second part of this series.

Image titleWe have learned about store, state, and Activity definitions in part 2, and started to explore Reducer. Let’s dive in…

Reducer

Reducer creates a copy of state and updates the needed members of the state class.

Reducer signature is simple, it accepts Action and State and returns a new State

fun someReducer(action: Action, state: AppState?): AppState 

Let’s create a Reducer for AuthenticationState. We will have an app level reducer, and each substate, such as AuthenticationState will have its own reducer.

fun appReducer(action: Action, oldState: GitHubAppState?) : GitHubAppState {

    // if no state has been provided, create the default state
    val state = oldState ?: GitHubAppState(
            authenticationState = AuthenticationState(
                loggedInState = LoggedInState.loggedIn,
                    userName = ""))

    return state.copy(
            authenticationState = (::authenticationReducer)(action, 
                                                            state.authenticationState))
}

fun authenticationReducer(action: Action, 
                          state: AuthenticationState?): AuthenticationState {

    val newState =  state ?: AuthenticationState(LoggedInState.notLoggedIn,
                                                 userName = "")
    when (action) {

        is LoginStartedAction -> {
            return newState.copy(isFetching = true)
        }
        is SomeOtherAction -> {
           // Do something with state
        }
    }
    return newState
}

The authenticationReducer function, when it encounters LoginStartedAction, creates a copy ofAuthenticationState and updates the class memberisFetching to true.

You may be wondering why the Activity has dispatched LoginAction and the reducer is handling the LoginStartedAction. Read on… you will understand the flow in the next section.

So far, we set up the Activity to subscribe and listen to the AuthenticationState.We need to call the GitHub API to make login page to work, isn’t?

Who in the unidirectional flow can take care of this responsibility?

Middleware

A native app cannot live in a vacuum; it needs data from external sources.

What are external data sources? APIs, Content providers, local databases like SQLite, CoreData.

When the action is been dispatched, if the action needs the data from external sources, it needs to be intercepted before it reaches the reducers. The middleware does the job of intercepting and doing the set of tasks before it reaches the reducers.

Middleware for API, Logging etc

The middleware could be used for other cross-cutting aspects like logging, routing, authentication, authorization, logging, gathering performance metrics, or some other decoration, and anything that falls into aspect-oriented programming (AOP). The middleware can be chained; it can be synchronous or asynchronous.

Let’s start defining the middleware for our app:

internal val gitHubMiddleware: Middleware<GitHubAppState> = { dispatch, getState ->
    { next ->
        { action ->
            when (action) {
                is LoginAction -> {
                    executeGitHubLoginTask(action,dispatch)
                }
                is SomeOtherAction -> {
                    // Do someother work
                }
            }
         next(action)
        }
    }
}

The middleware function signature looks a little tricky, right? Let’s try to demystify this:

typealias DispatchFunction = (Action) -> Unit
typealias Middleware<State> = (DispatchFunction, () -> State?) -> 
                         (DispatchFunction) -> DispatchFunction

A middleware function

  • Accepts two parameters: DispatchFunction and the function that returns the State the middleware function operates on.
  • Returns a function that accepts and returns DispatchFunction

Don't forget to update the creation of mainStore with middleware in the AppController class.

mainStore = Store(state = null,
        reducer = ::appReducer,
        middleware = arrayListOf(gitHubMiddleware))

When the LoginAction is intercepted, it calls the executeGitHubLogin function.

fun executeGitHubLogin(action: LoginAction, dispatch: DispatchFunction) {
    val loginTaskListenerMiddleware = LoginTaskListenerMiddleware()
    val authTask = UserLoginTask(loginTaskListenerMiddleware,
            action.userName,
            action.password )
    authTask.execute()
    dispatch(LoginStartedAction(action.userName))
}

interface LoginTaskListenerInterface {
    fun onFinished(result: LoginCompletedAction,store: Store<StateType>)
}

class LoginTaskListenerMiddleware : LoginTaskListenerInterface {
    override fun onFinished(result: LoginCompletedAction, store: Store<StateType>) {
      if (result.loginStatus == LoggedInState.loggedIn ) {
            result.token?.let {
               // Login succeed, do the next work
            }
        } else {
            result.message?.let{
               // Login failed, show an error message
            }
        }
    }
}

The function calls the regular AsyncTask and creates LoginStartedAction and dispatches it. The purpose of this Action is to let the Activity know that GitHubApi is called.

You may be wondering, why we cannot use LoginAction for this?

The name of the Action should describe itself, and the purpose of the LoginStartedAction is to let the Activity that the GitHub API is called and let it show the progressBar.

Redux Cycle

Just to make the Redux cycle clear, below is the code in the Activity that shows the progressBar based on the data class member isFetching

class LoginActivity : Activity(), StoreSubscriber<AuthenticationState> {

   // Other methods

    override fun newState(state: AuthenticationState) {
        if (state.isFetching) {
            ViewHelper.showProgress(show = true,
                        view = mViewForm,
                        progressView = mViewProgress,
                        resources = resources)
            } else {
                ViewHelper.showProgress(show = false,
                        view = mViewForm,
                        progressView = mViewProgress,
                        resources = resources)
            }         
        }  
}

In the AsyncTask, when onPostExecute is called, invokes the Listener to do the next task based on the result of the API call.

In the next post…

If you started worrying about interfaces and callbacks, a.k.a. listeners, hold on - we will discuss the alternatives such as RxKotlin or RxSwift in the next post.

Topics:
kotlin ,redux ,react ,swift ,mobile ,ios ,mobile app development

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}