Skip to content

Implementation of Redux basic tutorial in Kotlin and Java

Notifications You must be signed in to change notification settings

normartin/kotlin-redux

Repository files navigation

Redux with Kotlin and Java

Implementation of a Redux store and the Todo app from Redux's basic tutorial in Kotlin. Uses Kotlin's Flow for publishing updates.

Kotlin

// state
data class Todo(val text: String, val done: Boolean = false)

enum class VisibilityFilter { ALL, DONE, TODO }

data class TodoAppState(
    val todos: List<Todo> = emptyList(),
    val filter: VisibilityFilter = VisibilityFilter.ALL
)

// actions
sealed class Action {
    sealed class TodoAction : Action() {
        data class AddTodo(val text: String) : TodoAction()
        data class ToggleTodo(val index: Int) : TodoAction()
    }

    data class ChangeVisibility(val filter: VisibilityFilter) : Action()
}

// reducers
val todoReducer: Reducer<TodoAction, List<Todo>> = { action, todos ->
    when (action) {
        is AddTodo -> todos + Todo(action.text)
        is ToggleTodo -> todos.update(action.index) { it.copy(done = !it.done) }
    }
}
val appReducer: Reducer<Action, TodoAppState> = { action, state ->
    when (action) {
        is TodoAction -> state.copy(todos = todoReducer(action, state.todos))
        is ChangeVisibility -> state.copy(filter = action.filter)
    }
}

class RxStoreTest {

    @Test
    fun canGetCurrentStateOfStore() {
        val store = createStore(initialState = TodoAppState(), reducer = appReducer)

        store.dispatch(AddTodo("1"))
        assertThat(store.state().todos).containsExactly(Todo(text = "1", done = false))

        store.dispatch(ToggleTodo(0))
        assertThat(store.state().todos).containsExactly(Todo(text = "1", done = true))

        store.dispatch(ChangeVisibility(VisibilityFilter.DONE))
        assertThat(store.state().filter).isEqualTo(VisibilityFilter.DONE)
    }

    @Test
    fun canSubscribeToStoreUpdates() {
        runBlocking {
            val store = createStore(initialState = TodoAppState(), reducer = appReducer)

            val subscription: Flow<TodoAppState> = store.updates()

            subscription.test {
                assertThat(nextItem()).isEqualTo(TodoAppState())

                store.dispatch(AddTodo("1"))
                assertThat(nextItem().todos).containsExactly(Todo(text = "1", done = false))

                store.dispatch(ToggleTodo(0))
                assertThat(nextItem().todos).containsExactly(Todo(text = "1", done = true))

                store.dispatch(ChangeVisibility(VisibilityFilter.DONE))
                assertThat(nextItem().filter).isEqualTo(VisibilityFilter.DONE)

                cancelAndIgnoreRemainingEvents()
            }
        }
    }

    @Test
    fun canFilterOnSubTree() {
        runBlocking {
            val store = createStore(initialState = TodoAppState(), reducer = appReducer)

            val filterUpdates = store.updates().map { s -> s.filter }.distinctUntilChanged()

            filterUpdates.test {
                store.dispatch(ChangeVisibility(VisibilityFilter.DONE))
                expectNextItemEquals(VisibilityFilter.DONE)

                store.dispatch(AddTodo("1"))
                store.dispatch(ChangeVisibility(VisibilityFilter.DONE))
                expectNoEvents()

                store.dispatch(ChangeVisibility(VisibilityFilter.ALL))
                expectNextItemEquals(VisibilityFilter.ALL)

                cancelAndIgnoreRemainingEvents()
            }
        }
    }

    @Test
    fun throwingReducerDoesNotBreakStoreAndDoesNotEmitUpdate() {
        runBlocking {
            val store = createStore(10, { a: Int, s: Int -> s / a })

            val updates = store.updates()

            updates.test {
                expectNextItemEquals(10)

                store.dispatch(0)  // division by zero
                expectNoEvents()

                store.dispatch(10)
                assertThat(nextItem()).isEqualTo(1)

                cancelAndIgnoreRemainingEvents()
            }
        }
    }
    
    // ... more tests and examples
}

source

Store implementation

Kotlin with RX (Reactor)

RxStore implementation

Tests / usage

Java

Uses Vavr for immutable collections.

class JavaStoreTest {

    // state
    static class Todo {
        final String text;
        final boolean done;

        Todo(String text, boolean done) {
            this.text = text;
            this.done = done;
        }
    }

    enum VisibilityFilter {ALL, DONE, TODO}

    static class Todos {
        final List<Todo> todos;
        final VisibilityFilter filter;

        Todos(List<Todo> todos, VisibilityFilter filter) {
            this.todos = todos;
            this.filter = filter;
        }

        public static Todos initial() {
            return new Todos(List.empty(), VisibilityFilter.ALL);
        }
    }

    // actions
    interface Action {
    }

    static class AddTodo implements Action {
        final String text;

        AddTodo(String text) {
            this.text = text;
        }
    }

    static class ToggleTodo implements Action {
        final int index;

        ToggleTodo(int index) {
            this.index = index;
        }
    }

    static class ChangeVisibility implements Action {
        final VisibilityFilter filter;

        ChangeVisibility(VisibilityFilter filter) {
            this.filter = filter;
        }
    }

    // reducers
    Reducer<Action, Todos> addTodoReducer = reduceOn(AddTodo.class,
            (action, state) -> new Todos(state.todos.append(new Todo(action.text, false)), state.filter)
    );

    Reducer<Action, Todos> toggleReducer = reduceOn(ToggleTodo.class,
            (action, state) -> new Todos(state.todos.update(action.index, t -> new Todo(t.text, !t.done)), state.filter)
    );

    Reducer<Action, Todos> changeVisibilityReducer = reduceOn(ChangeVisibility.class,
            (action, state) -> new Todos(state.todos, action.filter)
    );

    Reducer<Action, Todos> reducer = combine(addTodoReducer, toggleReducer, changeVisibilityReducer);

    @Test
    void demo() {
        JavaStore<Action, Todos> store = createStore(Todos.initial(), reducer);

        store.updates.subscribe(s -> System.out.println("State changed " + s));

        store.dispatch(new AddTodo("1"));

        await().untilAsserted(() -> assertThat(store.state().todos).extracting(t -> t.text).containsExactly("1"));

        store.dispatch(new ToggleTodo(0));

        await().untilAsserted(() -> assertThat(store.state().todos).extracting(t -> t.done).containsExactly(true));

        store.dispatch(new ChangeVisibility(VisibilityFilter.DONE));

        await().untilAsserted(() -> assertThat(store.state().filter).isEqualTo(VisibilityFilter.DONE));
    }
}

source

Store implementation

About

Implementation of Redux basic tutorial in Kotlin and Java

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published