Skip to content

Commit a84b749

Browse files
committed
Add docs
1 parent 65addf2 commit a84b749

File tree

4 files changed

+126
-8
lines changed

4 files changed

+126
-8
lines changed

README.md

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,127 @@
1212

1313
This project started as a library module in one of my personal projects, but I decided to open source it and add more features for general use. Hope you like!
1414

15+
[![turnstile diagram](https://github.com/adrielcafe/hal/blob/master/turnstile-diagram.jpg?raw=true)](https://www.smashingmagazine.com/2018/01/rise-state-machines/)
16+
1517
## Usage
16-
### TODO
18+
19+
First, declare your `Action`s and `State`s. They **must** implement `HAL.Action` and `HAL.State` respectively.
20+
21+
```kotlin
22+
sealed class MyAction : HAL.Action {
23+
24+
object LoadPosts : MyAction()
25+
26+
data class AddPost(val post: Post) : MyAction()
27+
}
28+
29+
sealed class MyState : HAL.State {
30+
31+
object Init : MyState()
32+
33+
object Loading : MyState()
34+
35+
data class PostsLoaded(val posts: List<Post>) : MyState()
36+
37+
data class Error(val message: String) : MyState()
38+
}
39+
```
40+
41+
Next, implement the `HAL.StateMachine<YourAction, YourState>` interface in your `ViewModel`, `Presenter`, `Controller` or similar.
42+
43+
The `HAL` class receives the following parameters:
44+
* A [`CoroutineScope`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) (tip: use the [built in viewModelScope](https://developer.android.com/topic/libraries/architecture/coroutines#viewmodelscope))
45+
* A initial state
46+
* A reducer function, `suspend (action: A, transitionTo: (S) -> Unit) -> Unit`, where:
47+
- `suspend`: the reducer runs inside a coroutine scope on a [default dispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/), so you can run IO and other complex tasks without worrying about block the Main Thread
48+
- `action: A`: the action emitted to the state machine
49+
- `transitionTo: (S) -> Unit`: the function responsible for changing the state
50+
51+
You should handle all actions inside the reducer function. Call `transitionTo()` whenever you need to change the state (it can be called multiple times).
52+
53+
```kotlin
54+
class MyViewModel(private val postRepository: PostRepository)
55+
: ViewModel(), HAL.StateMachine<MyAction, MyState> {
56+
57+
override val hal by HAL(viewModelScope, MyState.Init) { action, transitionTo ->
58+
when (action) {
59+
is MyAction.LoadPosts -> {
60+
transitionTo(MyState.Loading)
61+
62+
try {
63+
// You can run suspend functions
64+
val posts = postRepository.getPosts()
65+
// And emit multiple states per action
66+
transitionTo(MyState.PostsLoaded(posts))
67+
} catch(e: Exception) {
68+
transitionTo(MyState.Error("Ops, something went wrong."))
69+
}
70+
}
71+
72+
is MyAction.AddPost -> {
73+
/* Handle action */
74+
}
75+
}
76+
}
77+
}
78+
```
79+
80+
Finally, inside your view class (`Activity`, `Fragment` or similar) you can emit actions to your state machine and observe state changes.
81+
82+
If you want to use a [built in LiveData state observer](https://github.com/adrielcafe/HAL/blob/master/hal-livedata/src/main/kotlin/cafe/adriel/hal/livedata/observer/LiveDataStateObserver.kt), just pass your `LifecycleOwner` to `viewModel.observeState()`, otherwise HAL will use a default [callback-based state observer](https://github.com/adrielcafe/HAL/blob/master/hal-core/src/main/kotlin/cafe/adriel/hal/observer/CallbackStateObserver.kt) (which is best suited for JVM-only applications).
83+
84+
```kotlin
85+
class MyActivity : AppCompatActivity() {
86+
87+
private val viewModel by viewModels<MyViewModel>()
88+
89+
override fun onCreate(savedInstanceState: Bundle?) {
90+
91+
// Easily emit actions to your State Machine
92+
loadPostsBt.setOnClickListener {
93+
viewModel + MyState.LoadPosts
94+
}
95+
96+
// Observe and handle state changes backed by a LiveData
97+
viewModel.observeState(this) { state ->
98+
when (state) {
99+
is MyState.Init -> showWelcomeMessage()
100+
101+
is MyState.Loading -> showLoading()
102+
103+
is MyState.PostsLoaded -> showPosts(state.posts)
104+
105+
is MyState.Error -> showError(state.message)
106+
}
107+
}
108+
}
109+
}
110+
```
111+
112+
### Custom StateObserver
113+
114+
If needed, you can easily create your custom state observers by just implementing the `StateObserver<State>` interface:
115+
116+
```kotlin
117+
class MyCustomStateObserver<S : HAL.State>(
118+
private val myCustomParam: SomeCoolClass,
119+
override val observer: (S) -> Unit
120+
) : StateObserver<S> {
121+
122+
override fun transitionTo(newState: S) {
123+
// Do any kind of operation and call `observer(newState)` in the end
124+
// IMPORTANT: this method runs on the Main Thread!
125+
}
126+
}
127+
```
128+
129+
And to use, just create an instance of it and pass to `observeState()` function:
130+
131+
```kotlin
132+
viewModel.observeState(MyCustomStateObserver(myCustomParam) { state ->
133+
// Handle state
134+
})
135+
```
17136

18137
## Import to your project
19138
1. Add the JitPack repository in your root build.gradle at the end of repositories:
@@ -40,6 +159,6 @@ Current version: [![JitPack](https://img.shields.io/jitpack/v/github/adrielcafe/
40159
### Platform compatibility
41160

42161
| | `hal-core` | `hal-livedata` |
43-
|---------|--------|-----------|
44-
| Android |||
45-
| JVM || |
162+
|---------|------------|----------------|
163+
| Android | | |
164+
| JVM | | |

sample/src/main/java/cafe/adriel/hal/sample/network/NetworkStateMachine.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sealed class NetworkAction : HAL.Action {
88

99
sealed class NetworkState : HAL.State {
1010
object Init : NetworkState()
11-
data class PostsLoaded(val posts: List<String>) : NetworkState()
1211
object Loading : NetworkState()
12+
data class PostsLoaded(val posts: List<String>) : NetworkState()
1313
data class Error(val message: String) : NetworkState()
1414
}

sample/src/main/java/cafe/adriel/hal/sample/turnstile/TurnstileViewModel.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import cafe.adriel.hal.HAL
66

77
class TurnstileViewModel : ViewModel(), HAL.StateMachine<TurnstileAction, TurnstileState> {
88

9-
override val hal by HAL(viewModelScope, TurnstileState.Locked, ::reducer)
10-
11-
private suspend fun reducer(action: TurnstileAction, transitionTo: (TurnstileState) -> Unit) =
9+
override val hal by HAL(viewModelScope, TurnstileState.Locked) { action, transitionTo ->
1210
when (action) {
1311
is TurnstileAction.InsertCoin -> transitionTo(TurnstileState.Unlocked)
1412
is TurnstileAction.Push -> transitionTo(TurnstileState.Locked)
1513
}
14+
}
1615
}

turnstile-diagram.jpg

30.7 KB
Loading

0 commit comments

Comments
 (0)