diff --git a/README.md b/README.md index 571f2b0ca..6a5d83b27 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,142 @@ 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(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 = ... + +// We convert Formula instance 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(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. diff --git a/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt b/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt index 62110da64..fb9a00361 100644 --- a/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt +++ b/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt @@ -9,7 +9,7 @@ class CounterFormulaTest { @Test fun `increment 5 times`() { CounterFormula() - .test(Unit) + .test() .output { onIncrement() } .output { onIncrement() } .output { onIncrement() }