Skip to content

Commit

Permalink
Merge pull request #15 from mori-atsushi/document
Browse files Browse the repository at this point in the history
Add code comments
  • Loading branch information
mori-atsushi authored Sep 16, 2023
2 parents dd12ac5 + e367344 commit dbe4029
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,60 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import com.moriatsushi.kredux.Store

/**
* Create a [CompositionLocal] key that can be provided using
* [androidx.compose.runtime.CompositionLocalProvider] for a [Store].
*
* An example for providing a [Store]:
* ```
* val LocalStore = localStoreOf<Store<State, Action>>()
*
* @Composable
* fun App() {
* val store = remember { Store(reducer) }
* CompositionLocalProvider(LocalStore provides store) {
* // content
* }
* }
* ```
*/
fun <T : Store<*, *>> localStoreOf(): ProvidableCompositionLocal<T> {
return staticCompositionLocalOf { error("No store provided") }
}

/**
* Extracts a value from the current state of the [Store] provided by [localStoreOf] using the
* [selector]. Only when the value is changed, the composable will be recomposed.
*
* An example of a composable that displays the current count:
* ```
* @Composable
* fun Counter() {
* val count by LocalCounterStore.select { it.count }
* Text(text = "Count: $count")
* }
* ```
*/
@Composable
fun <T : Store<S, *>, S, R> CompositionLocal<T>.select(selector: (S) -> R): State<R> {
val state by current.state.collectAsState()
return remember { derivedStateOf { selector(state) } }
}

/**
* Gets a dispatch lambda from the [Store] provided by [localStoreOf].
*
* An example of a composable that dispatches an action:
* ```
* @Composable
* fun IncrementButton() {
* val dispatch = LocalCounterStore.dispatch
* Button(onClick = { dispatch(Action.Increment) }) {
* Text(text = "Increment")
* }
* }
* ```
*/
val <T : Store<*, Action>, Action> CompositionLocal<T>.dispatch: (Action) -> Unit
@Composable
get() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
package com.moriatsushi.kredux

/**
* Turns two [Reducer]s into a single [Reducer] function. The resulting [Reducer] calls every
* child [Reducer], and gathers their results into a single state object with the [transform].
*
* An example:
* ```
* val reducer = combineReducers(
* child(reducer1) { it.child1 },
* child(reducer2) { it.child2 }
* ) { child1, child2 ->
* State(child1, child2)
* }
* ```
*
* The example above is equivalent to:
* ```
* val reducer = Reducer(
* State(reducer1.initialState, reducer2.initialState)
* ) { acc, action ->
* State(
* reducer1.reduce(acc.child1, action),
* reducer2.reduce(acc.child2, action),
* )
* )
* ```
*
* This can also map parent actions to child actions. If the `mapToChildAction]`returns `null`, the
* child reducer will not be called.
* ```
* val reducer = combineReducers(
* child(
* reducer1,
* mapToChildAction = { it as? ParentAction.Child1 },
* mapToChildState = { it.child1 }
* ),
* child(
* reducer2,
* mapToChildAction = { it as? ParentAction.Child2 },
* mapToChildState = { it.child2 }
* )
* ) { child1, child2 ->
* State(child1, child2)
* }
* ```
*/
fun <State, Action : Any, S1, S2> combineReducers(
reducer1: ChildReducer<State, Action, S1>,
reducer2: ChildReducer<State, Action, S2>,
Expand All @@ -11,6 +56,10 @@ fun <State, Action : Any, S1, S2> combineReducers(
}
}

/**
* Turns three [Reducer]s into a single [Reducer] function. The resulting [Reducer] calls every
* child [Reducer], and gathers their results into a single state object with the [transform].
*/
fun <State, Action : Any, S1, S2, S3> combineReducers(
reducer1: ChildReducer<State, Action, S1>,
reducer2: ChildReducer<State, Action, S2>,
Expand All @@ -23,6 +72,10 @@ fun <State, Action : Any, S1, S2, S3> combineReducers(
}
}

/**
* Turns four [Reducer]s into a single [Reducer] function. The resulting [Reducer] calls every
* child [Reducer], and gathers their results into a single state object with the [transform].
*/
fun <State, Action : Any, S1, S2, S3, S4> combineReducers(
reducer1: ChildReducer<State, Action, S1>,
reducer2: ChildReducer<State, Action, S2>,
Expand All @@ -41,6 +94,10 @@ fun <State, Action : Any, S1, S2, S3, S4> combineReducers(
}
}

/**
* Turns five [Reducer]s into a single [Reducer] function. The resulting [Reducer] calls every
* child [Reducer], and gathers their results into a single state object with the [transform].
*/
fun <State, Action : Any, S1, S2, S3, S4, S5> combineReducers(
reducer1: ChildReducer<State, Action, S1>,
reducer2: ChildReducer<State, Action, S2>,
Expand All @@ -61,6 +118,10 @@ fun <State, Action : Any, S1, S2, S3, S4, S5> combineReducers(
}
}

/**
* Turns multiple [Reducer]s into a single [Reducer] function. The resulting [Reducer] calls every
* child [Reducer], and gathers their results into a single state object with the [transform].
*/
fun <State, Action : Any, S> combineReducers(
vararg reducers: ChildReducer<State, Action, S>,
transform: (List<S>) -> State,
Expand All @@ -75,12 +136,26 @@ private fun <State, Action : Any> combineReducersUnsafe(
return CombinedReducer(reducers, transform)
}

/**
* A transformed [Reducer] for [combineReducers] that returns the next child state, given the
* parent state and an action to handle.
*/
interface ChildReducer<in ParentState, in Action : Any, out ChildState> {
/**
* An initial state.
*/
val initialState: ChildState

/**
* Returns the next child state, given the [parent state][parent] and an [action] to handle.
*/
fun reduce(parent: ParentState, action: Action): ChildState
}

/**
* Transforms the [Reducer] to a [ChildReducer] for [combineReducers]. The [mapToChildState]
* transforms the [ParentState] to the [ChildState].
*/
fun <ParentState, ParentAction : Any, ChildState> child(
reducer: Reducer<ChildState, ParentAction>,
mapToChildState: (parent: ParentState) -> ChildState,
Expand All @@ -95,6 +170,12 @@ fun <ParentState, ParentAction : Any, ChildState> child(
}
}

/**
* Transforms the [Reducer] to a [ChildReducer] for [combineReducers]. The [mapToChildAction]
* transforms the [ParentAction] to the [ChildAction], and the [mapToChildState] transforms the
* [ParentState] to the [ChildState]. If the [mapToChildAction] returns `null`, the [reducer]
* will not be called.
*/
fun <ParentState, ParentAction : Any, ChildState, ChildAction : Any> child(
reducer: Reducer<ChildState, ChildAction>,
mapToChildAction: (parent: ParentAction) -> ChildAction?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,58 @@ package com.moriatsushi.kredux

import kotlinx.coroutines.CoroutineScope

interface Middleware<State, Action : Any> {
/**
* A middleware that can wrap the [Store.dispatch] method.
*
* An example of a middleware that logs actions and states:
* ```
* class LoggerMiddleware<State, Action : Any> : Middleware<State, Action> {
* override fun MiddlewareScope<State, Action>.process(action: Action): State {
* println("action: $action")
* println("current state: $state")
* val nextState = next(action)
* println("next state: $nextState")
* return nextState
* }
* }
* ```
*/
fun interface Middleware<State, Action : Any> {
/**
* Processes an [action] and returns the next state. This function is given the
* [next][MiddlewareScope.next] function that is the next middleware function in the chain,
* and is expected to call `next(action)` with different arguments, or at different times,
* or maybe not call it at all. The last middleware in the chain will receive the real
* [Store.dispatch] function as the [next][MiddlewareScope.next], thus ending the chain.
*/
fun MiddlewareScope<State, Action>.process(action: Action): State
}

/**
* A scope for a [Middleware].
*
* **Do not implement this interface directly.** New methods or properties might be added to this
* interface in the future.
*/
interface MiddlewareScope<State, Action : Any> {
/**
* The current state of the [Store].
*/
val state: State

/**
* The [CoroutineScope] of the [Store].
*/
val coroutineScope: CoroutineScope

/**
* Calls the next middleware function in the chain, or the real [Store.dispatch] function if the
* middleware is the last in the chain.
*/
fun next(action: Action): State

/**
* Dispatches an [action]. It is the same as calling [Store.dispatch].
*/
fun dispatch(action: Action)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@ package com.moriatsushi.kredux

import com.moriatsushi.kredux.internal.ReducerImpl

/**
* A reducer that returns the next state, given the current state and an action to handle.
*
* It is not recommended to implement this interface directly. Use the `Reducer()` function to
* create a [Reducer].
*/
interface Reducer<State, in Action : Any> {
/**
* An initial state.
*/
val initialState: State

/**
* Returns the next state, given the [current state][acc] and an [action] to handle.
*/
fun reduce(acc: State, action: Action): State
}

/**
* Creates a [Reducer] that returns the next state, given the current state and an action to handle.
*
* An example of a reducer that represents a counter:
* ```
* val reducer = Reducer(0) { acc, action: Action ->
* when (action) {
* is Action.Increment -> acc + 1
* is Action.Decrement -> acc - 1
* }
* }
* ```
*/
fun <State, Action : Any> Reducer(
initialState: State,
reducer: (acc: State, action: Action) -> State,
Expand Down
25 changes: 25 additions & 0 deletions kredux-core/src/commonMain/kotlin/com/moriatsushi/kredux/Store.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,36 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow

/**
* A store that holds a [state][State]. The only way to change the state is to [dispatch] an
* [action][Action].
*
* **Do not implement this interface directly.** New methods or properties might be added to this
* interface in the future. Use the `Store()` function to create a [Store].
*/
interface Store<out State, in Action : Any> {
/**
* A [StateFlow] of the current state.
*/
val state: StateFlow<State>

/**
* Dispatches an [action][Action].
*/
fun dispatch(action: Action)
}

/**
* Creates a [Store] that holds the state.
*
* @param reducer A [Reducer] that returns the next state, given the current state and an action to
* handle.
* @param initialState An initial state. If not specified, the [Reducer.initialState] is used.
* @param middlewares A list of [Middleware]s that can wrap the [Store.dispatch] method.
* @param coroutineScope A [CoroutineScope] of the [Store]. If not specified, a global [CoroutineScope]
* is created.
* @return A [Store] that holds the state.
*/
fun <State, Action : Any> Store(
reducer: Reducer<State, Action>,
initialState: State = reducer.initialState,
Expand Down
Loading

0 comments on commit dbe4029

Please sign in to comment.