Skip to content

pocket7878/reduxy

Repository files navigation

reduxy

Reduxy like architecture for Android.

architecture

Usage

Add maven repository into your build.gradle:

repositories {
    ...
    jcenter()
    ...
}

Add dependency into your application-level build.gradle

dependencies {
    ...
    implementation 'jp.pocket7878.reduxy:reduxy:0.0.2'
    ...
}

Architecture

StateType

Application state data type tag.

Example

data class ApplicationState(
    val counter: Int,
    val progress: Int
) : StateType

StateFactory<T: StateType>

Create state instance.

interface StateFactory<T : StateType> {
    fun create(): T
}

Example

class ApplicationStateFactory : StateFactory<ApplicationState> {
    override fun create(): ApplicationState {
        return ApplicationState(0, 0)
    }
}

Navigation

Nagitation type tag to represent screen transition.

interface Navigation<T : StateType, E : ErrorTag> : Action<T, Navigation<T, E>, E>

Example

Can be combine with Navigation component.

interface Nav : Navigation<ApplicationState, ErrorTag> {
    data class Direction(val direction: NavDirections) : Nav
}

ErrorTag, ErrorEntry

Error type tag to represent application error.

interface ErrorTag

data class ErrorEntry<T : StateType, N : Navigation<T, E>, E : ErrorTag>(
    val tag: E,
    val cause: Throwable?,
    val extras: Any? = null,
    val recoverAction: (() -> Unit)? = null
) : Action<T, N, E>

Action

interface Action<T : StateType, N : Navigation<T, E>, E : ErrorTag>

Action type tag to grouping StateType, Navigation, ErrorTag.

Example

interface CounterAction : Action<ApplicationState, Nav, ErrorTag> {
    class CountUp : CounterAction
    class Reset : CounterAction
}

Reducer

Reducer to generate new state from current state and dispatched action.

interface Reducer<T : StateType, N : Navigation<T, E>, E : ErrorTag> {
    fun run(state: T, action: Action<T, N, E>): T
}

Example

class CounterReducer : Reducer<ApplicationState, Nav, ErrorTag> {
    override fun run(
        state: ApplicationState,
        action: Action<ApplicationState, Nav, ErrorTag>
    ): ApplicationState {
        return when (action) {
            is CounterAction.CountUp -> {
                state.copy(
                    counter = state.counter + 1
                )
            }
            is CounterAction.Reset -> {
                state.copy(
                    counter = 0
                )
            }
            else -> state
        }
    }
}

Utility

Also defined some utility functions:

  • fun <T : StateType, N : Navigation<T, E>, E : ErrorTag> Reducer<T, N, E>.compose(other: Reducer<T, N, E>): Reducer<T, N, E>
  • fun <T : StateType, N : Navigation<T, E>, E : ErrorTag> Collection<Reducer<T, N, E>>.compose(): Reducer<T, N, E>

Middleware

Middleware to filter, modify, or submit action before reducer.

typealias Dispatcher<T, N, E> = (Action<T, N, E>) -> Unit

interface Middleware<T : StateType, N : Navigation<T, E>, E : ErrorTag> {
    fun call(s: T): (Dispatcher<T, N, E>) -> Dispatcher<T, N, E>
}

Example

class LoggerMiddleware : Middleware<ApplicationState, Nav, ErrorTag> {
    override fun call(s: ApplicationState): (Dispatcher<ApplicationState, Nav, ErrorTag>) -> Dispatcher<ApplicationState, Nav, ErrorTag> {
        return { dispatcher ->
            { action ->
                Timber.d(action.toString())
                dispatcher(action)
            }
        }
    }
}

Store

Manage all redux component.

object Store {
    private val _instance by lazy {
        Store(
            ApplicationStateFactory(),
            listOf(CounterReducer(), SeekbarReducer()).compose(),
            listOf(LoggerMiddleware())
        )
    }

    fun getInstance(): Store<ApplicationState, Nav, ErrorTag> {
        return _instance
    }
}

Example App

Example application is stored in app/ directory

class FirstFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        binding.plusOneButton.setOnClickListener {
            Store.getInstance()
                .dispatch(CounterAction.CountUp())
        }

        binding.toSecondButton.setOnClickListener {
            Store.getInstance().navigate(
                Nav.Direction(
                    FirstFragmentDirections.actionFirstFragmentToSecondFragment()
                )
            )
        }

        Store.getInstance()
            .state()
            .map { it.counter.toString() }
            .distinctUntilChanged()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .autoDispose(this)
            .subscribe {
                binding.textView.text = it
            }
        ...
    }
    ...
}

Special thanks

Redux like architecture design idea is inspired by RettyEng/redux-kt.

About

Reduxy like architecture for Android.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages