From 2fff9202d699873c34ffdec02bc1ceb06b9118bc Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Wed, 23 Mar 2022 17:11:50 -0700 Subject: [PATCH] Updating readme. --- README.md | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/README.md b/README.md index 2d25cf0f..c8f545d0 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,160 @@ deterministic, composable, testable applications. [Check samples](samples) +## Quick Example +As a quick example, we'll show how a counter application looks which shows +current amount and allows you to increment or decrement it. +```kotlin +class CounterFormula : Formula() { + + data class State( + val currentCount: Int + ) + + data class Output( + val countText: String, + val onIncrement: () -> Unit, + val onDecrement: () -> Unit, + ) + + override fun initialState(input: Unit): State { + return State(currentCount = 0) + } + + override fun Snapshot.evaluate(): Evaluation { + val currentCount = state.currentCount + val output = Output( + countText = "Count: $currentCount", + onIncrement = context.callback { + val newState = state.copy( + currentCount = currentCount + 1 + ) + transition(newState) + }, + onDecrement = context.callback { + val newState = state.copy( + currentCount = currentCount - 1 + ) + transition(newState) + } + ) + return Evaluation(output = output) + } +} + +// Vanilla Android rendering logic +val currentCountTextView: TextView = ... +val incrementButton: Button = ... +val decrementButton: Button = ... + +// Converting to Coroutine Flow. +val counterOutput: Flow = CounterFormula().toFlow() +scope.launch { + counterOutput.collect { output -> + counterTextView.text = output.countText + incrementButton.setOnClickListener { output.onIncrement() } + decrementButton.setOnClickListener { output.onDecrement() } + } +} + +// Alternatively, converting to RxJava observable. +val counterOutput: Observable = CounterFormula().toObservable() +counterOutput.subscribe { output -> + counterTextView.text = output.countText + incrementButton.setOnClickListener { output.onIncrement() } + decrementButton.setOnClickListener { output.onDecrement() } +} +``` +## Quick Example - Testing +```kotlin +@Test fun `counter will not allow to decrement below 0`() { + CounterFormula() + .test() + .output { Truth.assertThat(count).isEqualTo("Count: 0") } + .output { onDecrement() } + // This fails right now as we don't have this logic added. + .output { Truth.assertThat(count).isEqualTo("Count: 0") } +} +``` + +## Quick Example - Async code +To display how to handle asynchronous actions, let's update previous counter example where +we persist and read current counter state from disk. +```kotlin +// We use simple in-memory implementation which could be changed to support real disk cache. +class CounterRepository { + private var count: Int = 0 + + fun saveCurrentCount(count: Int) { + this.count = count + } + + fun getCounterState(): Observable { + return Observable.fromCallable { count } + } +} + +class CounterFormula(val repository: CounterRepository) : Formula() { + + // We make currentCount nullable to indicate not-loaded / loaded states. + data class State( + val currentCount: Int? + ) + + data class Output( + val countText: String, + val onIncrement: () -> Unit, + val onDecrement: () -> Unit, + ) + + override fun initialState(input: Unit): State { + return State(currentCount = null) + } + + override fun Snapshot.evaluate(): Evaluation { + val currentCount = state.currentCount + val output = if (currentCount == null) { + Output( + countText = "Loading", + onIncrement = {}, + onDecrement = {} + ) + } else { + Output( + countText = "Count: $currentCount", + onIncrement = context.callback { + val newState = state.copy( + currentCount = currentCount + 1 + ) + transition(newState) { + repository.saveCurrentCount(newState.currentCount) + } + }, + onDecrement = context.callback { + val newState = state.copy( + currentCount = currentCount - 1 + ) + transition(newState) { + repository.saveCurrentCount(newState.currentCount) + } + } + ) + } + + return Evaluation( + output = output, + actions = context.actions { + // We add an action which gets the counter state and updates our state. + RxAction.fromObservable { repository.getCounterState() }.onEvent { currentCount -> + val newState = state.copy(currentCount = currentCount) + transition(newState) + } + } + ) + } +} +``` + ## Android Module The Android module provides declarative API to connect reactive state management to Android Fragments. This module has been designed for gradual adoption. You can use as much or as little of it as you like.