Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding simple examples to README.md #268

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Unit, State, Output>() {

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<Unit, State>.evaluate(): Evaluation<Output> {
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.Output> = 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.Output> = CounterFormula().toObservable()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can show the example without using RxJava. Possible? Or maybe this should switch to Flow, might be more familiar to Android devs now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Flow example as well

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<Int> {
return Observable.fromCallable { count }
}
}

class CounterFormula(val repository: CounterRepository) : Formula<Unit, State, Output>() {

// 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<Unit, State>.evaluate(): Evaluation<Output> {
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)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to use Flow here as well yet? I don't think we support it yet, but if we do, maybe we could add that example too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, though I'm not yet sure how to best incorporate in in examples as there is RxJava as well. I will think about it more

}
)
}
}
```

## 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.
Expand Down