This is a framework library for developing applications on the JVM based on the unidirectional dataflow model.
flowchart LR
A[First State] -->|Input| B{Action} -->|Mutate| C(Model) --> D[Renderer] -->|Derive| E[Next State] -.->|Input| B
B -->|Trigger| F([Effect]) --> B
Please click here for generated documentation.
This brief example is missing some glue code to provide a quick feel of the library mechanics.
data class ScoreScreenModel(
val user: User? = null,
val isLoading: Boolean = false,
val throwable: Throwable? = null,
)
data class ScoreScreenState(
val playerName: String,
val score: String,
val isLoading: Boolean,
val errorMessage: String?,
) : ViewState<ScoreScreenModel, ScoreScreenDependency>
@Composable
fun ScoreScreenView(args: ScoreScreenArgs?) {
LoopView(
builder = ScoreScreenLoop,
args = args,
firstAction = OnLoopStart,
) {
Column {
if (isLoading) {
Text("Loading...")
} else if (errorMessage != null) {
Text(errorMessage)
Button(onClick = { emit(OnUpdateScoreClick) }) {
Text("Retry")
}
} else {
Text(playerName)
Text(score)
Button(onClick = { emit(OnUpdateScoreClick) }) {
Text("Update score")
}
}
}
}
}
class ScoreScreenRenderer : Renderer<ScoreScreenModel, ScoreScreenState> {
fun renderState(model: ScoreScreenModel) = with(model) {
ScoreScreenState(
playerName = if (user == null) "N/A" else user.nickname,
score = if (user == null) "N/A" else user.score.roundToInt().toString(),
errorMessage = throwable?.run { message ?: "Unknown error" },
isLoading = isLoading,
)
}
}
data object OnUpdateScoreClick : ScoreScreenAction {
override fun ScoreScreenModel.proceed() =
outcome(copy(isLoading = true, throwable = null), UpdateScore(user.id))
}
data class UpdateScoreSuccess(val user: User) : ScoreScreenAction {
override fun ScoreScreenModel.proceed() =
mutate(copy(user = user, isLoading = false))
}
data class UpdateScoreError(val throwable: Throwable) : ScoreScreenAction {
override fun ScoreScreenModel.proceed() =
mutate(copy(throwable = throwable, isLoading = false))
}
data class UpdateScore(val userId: String) : ScoreScreenEffect {
override suspend fun ScoreScreenEmitter.trigger(dependency: ScoreScreenDependencv) {
try {
val user = scoreService.getUserScore(userId)
emit(UpdateScoreSuccess(user))
} catch (throwable: Throwable) {
emit(UpdateScoreError(throwable))
}
}
}
Name | Description |
---|---|
Model | Holds data for business logic |
ViewState | UI state derived from the Model |
Renderer | Uses the Model to create new State for the UI |
Action | Mutates the Model and can trigger (any) Effect |
Effect | Does background work and triggers (any) Action |
Loop | Handles Action and Effect |
Packages are published to Maven Central, so make sure to add it to your list of repositories.
repositories {
mavenCentral()
}
dependencies {
// core multiplatform package
implementation("com.ekezet.hurok:base:2.0.0")
// library for using hurok with Compose Multiplatform
implementation("com.ekezet.hurok:compose:2.0.0")
// library for testing hurok-based applications
implementation("com.ekezet.hurok:test:2.0.0")
}
[versions]
hurok = "2.0.0
[libraries]
hurok-base = { group = "com.ekezet.hurok", name = "base", version.ref = "hurok" }
hurok-compose = { group = "com.ekezet.hurok", name = "compsoe", version.ref = "hurok" }
hurok-test = { group = "com.ekezet.hurok", name = "test", version.ref = "hurok" }
- Kotlin Multiplatform
- Kotlin Coroutines
- Compose Multiplatform
- Android SDK
For code samples please see Othello for Android.