Skip to content

Commit

Permalink
[Enhancement] Remove Identifier interface entirely (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
kittinunf committed Jul 15, 2021
1 parent 419d2e6 commit 0594235
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 62 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ interface CommentRepository {
Redux with CoRed implementation (the setup part should be under 35~ lines)

```kotlin
class CommentsState(val comments: List<String>? = null) : State
// State definition for you application
class CommentsState(val comments: List<String>? = null)

// Actions
object Load
class SetComments(val comments: List<String>?)

Expand All @@ -73,12 +75,12 @@ val store = Store(
scope = viewScope,
initialState = CommentsState(),
reducers = mapOf(
"SetComments" to Reducer { currentState: CommentsState, action: SetComments -> // This reducer is connected with SetComments action by using "SetComments" string as a Key
SetComments::class to Reducer { currentState: CommentsState, action: SetComments -> // This reducer is connected with SetComments action by using SetComments::class as a Key
currentState.copy(comments = action.comments)
}
),
middlewares = mapOf(
"Load" to Middleware { order: Order, store: Store, state: CommentsState, action: Load ->
Load::class to Middleware { order: Order, store: Store, state: CommentsState, action: Load -> // This middleware is connected with Load action by using Load::class as a Key
if (order == Order.AfterReduced) {
scope.launch {
val result = repository.getComments()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,19 @@ package com.github.kittinunf.cored

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlin.reflect.KClass

interface Identifiable {

val identifier: String
}

typealias ReducerType<S, A> = Pair<String, Reducer<S, A>>
typealias EffectType<S, A> = Pair<String, Middleware<S, A>>

private object SetStateActionIdentifiable : Identifiable {
// prepend with 2 underscores so it won't collide with the client identifier string
override val identifier: String = "__SetState"
}
typealias ReducerType<S, A> = Pair<KClass<out Any>, Reducer<S, A>>
typealias EffectType<S, A> = Pair<KClass<out Any>, Middleware<S, A>>

@Suppress("FunctionName")
private fun <S : Any> SetStateReducerType(): ReducerType<S, Any> = SetStateActionIdentifiable.identifier to SetStateReducer()
private fun <S : Any> SetStateReducerType(): ReducerType<S, Any> = SetStateAction::class to SetStateReducer()

@Suppress("FunctionName")
fun <S : Any, A : Any> Store(
scope: CoroutineScope = GlobalScope,
initialState: S,
reducers: Map<String, Reducer<S, A>>
reducers: Map<KClass<out Any>, Reducer<S, A>>
): StoreType<S> {
return StoreAdapter(Store(scope, initialState, StoreAdapterEngine((reducers + SetStateReducerType()).toMutableMap(), mutableMapOf())))
}
Expand All @@ -32,21 +23,19 @@ fun <S : Any, A : Any> Store(
fun <S : Any, A : Any> Store(
scope: CoroutineScope = GlobalScope,
initialState: S,
reducers: Map<String, Reducer<S, A>>,
middlewares: Map<String, Middleware<S, A>>
reducers: Map<KClass<out Any>, Reducer<S, A>>,
middlewares: Map<KClass<out Any>, Middleware<S, A>>
): StoreType<S> {
return StoreAdapter(Store(scope, initialState, StoreAdapterEngine((reducers + SetStateReducerType()).toMutableMap(), middlewares.toMutableMap())))
}

private class StoreAdapterEngine<S : Any, A : Any>(
val reducerMap: MutableMap<String, Reducer<S, A>>,
val middlewareMap: MutableMap<String, Middleware<S, A>>
val reducerMap: MutableMap<KClass<out Any>, Reducer<S, A>>,
val middlewareMap: MutableMap<KClass<out Any>, Middleware<S, A>>
) : StateScannerEngine<S> {

override suspend fun scan(storeType: StoreType<S>, state: S, action: Any): S {
// check whether it is identifiable or it is a SetStateAction
val id = if (action is SetStateAction<*>) SetStateActionIdentifiable else action as? Identifiable
val identifier = id?.identifier ?: action::class.simpleName!!
val identifier = action::class

val middleware = middlewareMap[identifier]
val reducer = reducerMap.getValue(identifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,13 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail

// State definition
data class CounterState(val counter: Int = 0)

typealias CounterAction = Any

class Increment(val by: Int) : CounterAction(), Identifiable {
override val identifier: String = "inc"
}

class Decrement(val by: Int) : CounterAction(), Identifiable {
override val identifier: String = "dec"
}

// no need to implement Identifier if you don't want to customize it, by default it is class::simpleName
class Set(val value: Int) : CounterAction()
// Action definition
class Increment(val by: Int)
class Decrement(val by: Int)
class Set(val value: Int)

typealias CounterStore = StoreType<CounterState>

Expand Down Expand Up @@ -291,8 +284,8 @@ class ReduxTest {
assertEquals(310, store.currentState.counter)
}

class Multiply(val by: Int) : CounterAction()
class Divide(val by: Int) : CounterAction()
class Multiply(val by: Int)
class Divide(val by: Int)

@Test
fun `should ignore action that is not unknown with the current known action reducer`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ class StoreAdapterTest {

private val testScope = CoroutineScope(Dispatchers.Unconfined)
private val reducers = mapOf(
"inc" to Reducer { currentState: CounterState, action: Increment ->
Increment::class to Reducer { currentState: CounterState, action: Increment ->
currentState.copy(counter = currentState.counter + action.by)
},
"dec" to Reducer { currentState: CounterState, action: Decrement ->
Decrement::class to Reducer { currentState: CounterState, action: Decrement ->
currentState.copy(counter = currentState.counter - action.by)
}
)
Expand Down Expand Up @@ -152,17 +152,15 @@ class StoreAdapterTest {

val sideEffectData = SideEffectData(100)

val middlewares = mapOf(
"inc" to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
val localStore = Store(testScope, CounterState(), reducers, mapOf(
Increment::class to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
if (order == Order.BeforeReduce) {
assertEquals(0, state.counter)
} else {
sideEffectData.value = sideEffectData.value + state.counter
}
}
)

val localStore = Store(testScope, CounterState(), reducers, middlewares)
))

runBlockingTest {
localStore.states
Expand All @@ -184,17 +182,15 @@ class StoreAdapterTest {

@Test
fun `should invoke middleware in the correct order`() {
val middlewares = mapOf(
"inc" to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
val localStore = Store(testScope, CounterState(), reducers, mapOf(
Increment::class to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
if (order == Order.BeforeReduce) {
assertEquals(0, state.counter)
} else {
assertEquals(100, state.counter)
}
}
)

val localStore = Store(testScope, CounterState(), reducers, middlewares)
))

runBlockingTest {
localStore.states
Expand All @@ -208,13 +204,11 @@ class StoreAdapterTest {

@Test
fun `should invoke even we don't provide the customization on the identifier with the qualified name`() {
val reducers = mapOf(
"Set" to Reducer { currentState: CounterState, action: Set ->
val localStore = Store(testScope, CounterState(), mapOf(
Set::class to Reducer { currentState: CounterState, action: Set ->
currentState.copy(counter = action.value)
}
)

val localStore = Store(testScope, CounterState(), reducers)
))

runBlockingTest {
localStore.states
Expand All @@ -230,8 +224,8 @@ class StoreAdapterTest {

@Test
fun `should be able to dispatch action from the middleware`() {
val middlewares = mapOf(
"inc" to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
val localStore = Store(testScope, CounterState(), reducers, mapOf(
Increment::class to Middleware { order: Order, store: CounterStore, state: CounterState, action: Increment ->
if (order == Order.AfterReduced) {
if (state.counter == 100) {
// dispatch another action from middleware
Expand All @@ -243,9 +237,7 @@ class StoreAdapterTest {
}
}
}
)

val localStore = Store(testScope, CounterState(), reducers, middlewares)
))

runBlockingTest {
localStore.states
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
MINGW* )
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
Expand Down

0 comments on commit 0594235

Please sign in to comment.