Skip to content

DaftMobile/ReduKt

Repository files navigation

ReduKt

Maven Central Kotlin build GitHub API reference

ReduKt is a Redux pattern adaptation in Kotlin, integrated with coroutines, testable and multiplatform. Implemented store provides easy & powerful way of extending its functionality - dispatch closure. It is used to deliver customizable extensions that help you reduce the boilerplate according to your needs!

Materials

If you want to see basic examples, go to overview section. More in-depth guides and examples are available for each library in their corresponding README files. They are easily accessible from libraries section.

API reference is available here.

Libraries

  • ReduKt Core
    • Redux store implementation with middlewares support.
    • Wide range of middleware builders.
    • Tools to ensure single-threaded usage.
    • Coroutines support - easy launching/joining/cancelling coroutines logically associated with actions.
    • Dispatch closure - simple mechanism to extend store's functionality (e.g. integration with DI frameworks)
  • ReduKt Test
    • Dispatch spying tools with wide range of assertions.
    • Middlewares testing tools.
    • TestStore implementation for verifying interactions with a store.
  • ReduKt Thunk + ReduKt Thunk Test - Redux Thunk adaptation
  • ReduKt Data - fetching data from external data sources with generic actions as a result.
  • ReduKt Data Ktor - integration with Ktor Client.
  • ReduKt Koin - integration with Koin framework.
  • ReduKt Compose - integration with Jetpack Compose.
  • ReduKt Swift - store wrapper that provides better experience on the Swift side.

Dependencies

If you want to use more than redukt-core it's a good idea to add redukt-bom:

val commonMain by getting {
  dependencies {
    implementation(platform("com.daftmobile.redukt:redukt-bom:1.0"))
    implementation("com.daftmobile.redukt:redukt-core")
    implementation("com.daftmobile.redukt:redukt-compose") // in your compose project
    implementation("com.daftmobile.redukt:redukt-data")
    implementation("com.daftmobile.redukt:redukt-data-ktor")
    implementation("com.daftmobile.redukt:redukt-koin")
    implementation("com.daftmobile.redukt:redukt-thunk")
  }
}
val commonTest by getting {
  dependencies {
    implementation(kotlin("test"))
    implementation("com.daftmobile.redukt:redukt-test")
    implementation("com.daftmobile.redukt:redukt-test-thunk")
  }
}
val iosMain by getting {
  dependencies {
    implementation("com.daftmobile.redukt:redukt-swift") // in any darwin source set
  }
}

Overview

Every Redux app starts with creating a store. In ReduKt here is how it's done:

data class AppState(val posts: List<Post> = emptyList())

fun appReducer(state: AppState, action: Action) = AppState(posts = postsReducer(state.posts, action))

// More flexible store creation with DSL!
val store = buildStore {
    AppState() reducedBy ::appReducer
    middlewares {
        if (IS_DEBUG) +debugMiddleware
        +postsMiddleware
    }
    // Extending store's functionality with DispatchClosure mechanism!
    closure {
        +GlobalKoinDI // Ready-to-use integration with Koin!
    }
}

State changes are delivered by the StateFlow:

// Collecting whole state...
store.state.collect { /* ... */ }
// ...or just a part of it!
store.select { it.posts }.collect { /* ... */ }

Customizable selectors that enables performance improvements:

val totalProductsPriceSelector = Selector(
  stateEquality = { old, new -> old.products == new.products },
  selector = { it.products.sumBy(Product::price) }
)
store.select(totalProductsPriceSelector).collect { 
    // Total prices is recalculated only if products have changed and when value is really needed!
}

Declaring middlewares comes with predefined utils:

// Standard middleware declaration. In comparison to Redux JS it has only 1 nested lambda.
// All the operations are available from lambda receiver and do not require declaring them as parameters!
val debugMiddleware: Middleware<AppState> = {
    { action ->
        println(action)
        next(action)
    }
}
// You can declare it shorter with `middleware` util
val debugMiddleware = middleware<AppState> { action ->
    println(action)
    next(action)
}
// ...or even shorter with `translucentMiddleware`
val debugMiddleware = translucentMiddleware<AppState> { action -> println(action) }

Launching and controlling coroutines is integrated with the store:

fun fetchPostsMiddleware(client: HttpClient) = consumingMiddleware<AppState, PostAction.FetchAll> {
    launchForeground {
        runCatching { client.get("/posts") }
            .onSuccess { dispatch(PostAction.FetchSuccess(it)) }
            .onFailure { dispatch(PostAction.FetchFailure(it)) }
    }
}
// ...
val job = store.dispatchJob(PostAction.FetchAll)
// ...
job.cancel()

Http client instance might be injected thanks to ReduKt Koin:

val fetchPostsMiddleware = consumingMiddleware<AppState, PostAction.FetchAll> {
    val httpClient by koin.inject<HttpClient>()
    launchForeground {
        // ...
    }
}

You might reduce amount of required middlewares with ReduKt Thunk:

object FetchAllPosts : BaseCoThunk<AppState>({
    val client by koin.inject<HttpClient>()
    runCatching { client.get("/posts") }
        .onSuccess { dispatch(PostAction.FetchSuccess(it)) }
        .onFailure { dispatch(PostAction.FetchFailure(it)) }
})
// ...
val job = store.dispatchJob(FetchAllPosts)
// ...
job.cancel()

To reduce even more code with asynchronous data sources, you can use ReduKt Data!

Multiplatform

This library is highly dependent on Kotlin Coroutines and therefore supports the same range of targets.

Targets list is available here.

About

ReduKt is a Redux pattern adaptation in Kotlin, integrated with coroutines, testable and multiplatform.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages