@@ -29,6 +29,7 @@ machine.processEvent(YellowEvent)
29
29
## Create state machine
30
30
31
31
First we create a state machine with one of those factory functions:
32
+
32
33
* ` createStateMachine() ` suspendable version (from ` kstatemachine-coroutines ` artifact)
33
34
* ` createStateMachineBlocking() ` blocking version (from ` kstatemachine-coroutines ` artifact)
34
35
* ` createStdLibStateMachine() ` - creates machine without Kotlin Coroutines support (from ` kstatemachine ` artifact)
@@ -42,7 +43,9 @@ val machine = createStateMachine(
42
43
}
43
44
```
44
45
45
- By default, ` createStateMachine() ` starts state machine. You can control it using ` start ` argument.
46
+ By default, factory functions start state machine. You can control it using ` start ` argument.
47
+
48
+ Subsequent samples will use ` createStateMachine() ` function, but you can choose that one which fits your needs.
46
49
47
50
## Setup states
48
51
@@ -56,7 +59,7 @@ using [state subclasses](#state-subclasses).
56
59
In state machine setup block define states with ` initialState() ` , ` state() ` and ` finalState() ` functions:
57
60
58
61
``` kotlin
59
- createStateMachine {
62
+ createStateMachine(scope) {
60
63
// Use initialState() function to create initial State and add it to StateMachine
61
64
// State machine enters this state after setup is complete
62
65
val greenState = initialState()
@@ -71,7 +74,7 @@ createStateMachine {
71
74
You can use ` setInitialState() ` function to set initial state separately:
72
75
73
76
``` kotlin
74
- createStateMachine {
77
+ createStateMachine(scope) {
75
78
val greenState = state()
76
79
setInitialState(greenState)
77
80
// ...
@@ -87,7 +90,7 @@ Subclass `DefaultState`, `DefaultFinalState` or their [data](#typesafe-transitio
87
90
``` kotlin
88
91
class SomeState : DefaultState ()
89
92
90
- createStateMachine {
93
+ createStateMachine(scope) {
91
94
val someState = addState(SomeState ())
92
95
// ...
93
96
}
@@ -163,7 +166,7 @@ on an application business logic like with [conditional transitions](#conditiona
163
166
and less flexibility:
164
167
165
168
``` kotlin
166
- createStateMachine {
169
+ createStateMachine(scope) {
167
170
lateinit var yellowState: State
168
171
169
172
greenState {
@@ -217,7 +220,7 @@ There might be many transitions from one state to another. It is possible to lis
217
220
setup block:
218
221
219
222
``` kotlin
220
- createStateMachine {
223
+ createStateMachine(scope) {
221
224
// ...
222
225
onTransition {
223
226
// Listen to all triggered transitions here
@@ -343,7 +346,7 @@ You can enable internal state machine logging on your platform.
343
346
On JVM:
344
347
345
348
``` kotlin
346
- createStateMachine {
349
+ createStateMachine(scope) {
347
350
logger = StateMachine .Logger { lazyMessage ->
348
351
println (lazyMessage())
349
352
}
@@ -354,9 +357,9 @@ createStateMachine {
354
357
On Android:
355
358
356
359
``` kotlin
357
- createStateMachine {
358
- logger = StateMachine .Logger { lazyMessage ->
359
- Log .d(this ::class .qualifiedName, lazyMessage())
360
+ createStateMachine(scope) {
361
+ logger = StateMachine .Logger { lazyMessage ->
362
+ Log .d(this ::class .qualifiedName, lazyMessage())
360
363
}
361
364
// ...
362
365
}
@@ -391,7 +394,7 @@ Notifications about finishing are available in two forms:
391
394
1 . Triggering of ` onFinished() ` listener callback. This is the only option for ` StateMachine ` .
392
395
393
396
``` kotlin
394
- val machine = createStateMachine {
397
+ val machine = createStateMachine(scope) {
395
398
val final = finalState(" final" )
396
399
setInitialState(final)
397
400
@@ -404,7 +407,7 @@ Notifications about finishing are available in two forms:
404
407
for performing transitions on finishing:
405
408
406
409
```kotlin
407
- createStateMachine {
410
+ createStateMachine(scope) {
408
411
val state2 = state(" state2" )
409
412
410
413
initialState(" state1" ) {
@@ -433,7 +436,7 @@ To create nested states simply use same functions (`state()`, `initialState()` e
433
436
setup block:
434
437
435
438
```kotlin
436
- val machine = createStateMachine {
439
+ val machine = createStateMachine(scope) {
437
440
val topLevelState = initialState {
438
441
// ...
439
442
val nestedState = initialState {
@@ -459,7 +462,7 @@ A child state can override an inherited transition. To override parent transitio
459
462
transition that matches the event.
460
463
461
464
```kotlin
462
- createStateMachine {
465
+ createStateMachine(scope) {
463
466
val state2 = state(" state2" )
464
467
// all nested states inherit this parent transition
465
468
transition<SwitchEvent > { targetState = state2 }
@@ -500,7 +503,7 @@ Set `childMode` argument of a state machine, or a state creation functions to `C
500
503
with parallel child mode is entered or exited, all its child states will be simultaneously entered or exited:
501
504
502
505
```kotlin
503
- createStateMachine(childMode = ChildMode .PARALLEL ) {
506
+ createStateMachine(scope, childMode = ChildMode .PARALLEL ) {
504
507
state(" Charger" ) {
505
508
initialState(" Charging" ) { /* ... */ }
506
509
state(" OnBattery" ) { /* ... */ }
@@ -539,7 +542,7 @@ You can specify default state which will be used if history was not recorded yet
539
542
When default state is not specified, parent initial state will be entered on transition to history state.
540
543
541
544
```kotlin
542
- val machine = createStateMachine {
545
+ val machine = createStateMachine(scope) {
543
546
state {
544
547
val state11 = initialState()
545
548
val state12 = state()
@@ -562,7 +565,7 @@ from defining a transition with incompatible data type parameters of event and t
562
565
```kotlin
563
566
class StringEvent(override val data: String) : DataEvent<String>
564
567
565
- createStateMachine {
568
+ createStateMachine(scope) {
566
569
val state2 = dataState<String> {
567
570
onEntry { println("State data: $data ") }
568
571
}
@@ -602,7 +605,7 @@ simpler to use event argument. You can specify arbitrary argument with an event
602
605
can get this argument in a state and transition listeners.
603
606
604
607
```kotlin
605
- val machine = createStateMachine {
608
+ val machine = createStateMachine(scope) {
606
609
state(" offState" ).onEntry {
607
610
println (" Event ${it.event} argument: ${it.argument} " )
608
611
}
@@ -639,7 +642,7 @@ By default, state machine simply ignores events that does not match any defined
639
642
logging is enabled or use custom `IgnoredEventHandler ` for example to throw error:
640
643
641
644
```kotlin
642
- createStateMachine {
645
+ createStateMachine(scope) {
643
646
// ...
644
647
ignoredEventHandler = StateMachine .IgnoredEventHandler {
645
648
error(" unexpected ${it.event} " )
@@ -662,7 +665,7 @@ thrown. Alternatively with custom `PendingEventHandler` you can post such events
662
665
passing to `processEvent()`. Using of throwing `PendingEventHandler ` sample:
663
666
664
667
```kotlin
665
- createStateMachine {
668
+ createStateMachine(scope) {
666
669
// ...
667
670
pendingEventHandler = throwingPendingEventHandler()
668
671
}
@@ -687,38 +690,47 @@ Calling `processEvent()` on destroyed machine will throw also.
687
690
688
691
## Multithreading and concurrency
689
692
690
- KStateMachine is designed to work in single thread.
693
+ KStateMachine is designed to work in single thread.
691
694
Concurrent modification of library classes will lead to race conditions.
692
695
693
696
## Kotlin Coroutines
694
697
695
698
Starting from `KStateMachine v0.20.0 ` the library has built- in coroutines support.
696
699
All its callbacks and other APIs were marked with `suspend ` modifier, allowing to use coroutines from them.
697
- You can still use all KStateMachine features without Kotlin Coroutines library dependency as `suspend ` keyword
700
+ You can still use all KStateMachine features without Kotlin Coroutines library dependency as `suspend ` keyword
698
701
is implemented at compiler level and Coroutines library is not really necessary to start coroutines.
699
702
700
- TODO work in progress
703
+ Many functions like `createStateMachine`/ `start`/ `stop`/ `processEvent`/ `undo` etc. are suspendable, but all of them
704
+ has analogs with `Blocking ` suffix which are not marked with `suspend ` keyword.
705
+ If you use KStateMachine with coroutines support you should prefer suspendable function versions.
706
+ Note that `Blocking ` versions internally use `kotlinx.coroutines.runBlocking` function which is rather dangerous and
707
+ may cause deadlocks if used not properly. That is why you should avoid using `Blocking ` APIs from coroutines and
708
+ recursively (from library callbacks).
709
+
710
+ Such suspendable functions preserve state machines coroutine context (using `kotlinx.coroutines.withContext`),
711
+ so it should be ok to call them from any thread.
701
712
702
713
### Migration guide from versions older than v0.20.0
703
714
704
715
#### If you already have or ready to add Kotlin Coroutines dependency
705
716
706
717
* Add both `kstatemachine` and `kstatemachine- coroutines` artifacts to your build system
707
- * Use `createStateMachine` from `kstatemachine- coroutines` artifact to create state machines
708
- providing `CoroutineScope ` as argument
709
- * Use suspendable versions of functions (`start`/ `stop`/ `processEvent` etc.) when possible
718
+ * Use `createStateMachine` or `createStateMachineBlocking` from `kstatemachine- coroutines` artifact to create state
719
+ machines providing `CoroutineScope ` as argument
720
+ * Use suspendable versions of functions (`start`/ `stop`/ `processEvent`/ `undo` etc.) when possible
710
721
* Avoid using function analogs with `Blocking ` suffix ** (especially recursively)** as this may easily lead to deadlocks
711
- or race conditions depending on your use case and machine configuration
722
+ or race conditions depending on your use case and machine configuration
712
723
713
724
#### If you can not have dependency on Kotlin Coroutines or just do not want to use it
714
725
715
726
* Use only `kstatemachine` artifact in your build system
716
727
* Use `createStdLibStateMachine` to create state machines
717
- * Use suspendable versions of functions (`start`/ `stop`/ `processEvent` etc.) when possible (from KStateMachine callbacks)
728
+ * Use suspendable versions of functions (`start`/ `stop`/ `processEvent`/ `undo` etc.) when possible
729
+ (from KStateMachine callbacks for example)
718
730
* In other cases use their analogs with `Blocking ` suffix, it is ok
719
731
* If you try to use Kotlin Coroutines library from machine created by `createStdLibStateMachine` you will probably get
720
- an exception.
721
- * Using suspendable code without calls to Kotlin Coroutines library is ok, as `suspend ` keyword is a compiler feature,
732
+ an exception.
733
+ * Using suspendable code without calls to Kotlin Coroutines library is ok, as `suspend ` keyword is a compiler feature,
722
734
not library one.
723
735
724
736
## Export
@@ -729,11 +741,11 @@ may touch application data that is not valid when export is running._
729
741
730
742
### PlantUML
731
743
732
- Use `exportToPlantUml()/ exportToPlantUmlBlocking()` extension function to export state machine
744
+ Use `exportToPlantUml()` / ` exportToPlantUmlBlocking()` extension function to export state machine
733
745
to [PlantUML state diagram](https: // plantuml.com/en/state-diagram).
734
746
735
747
```kotlin
736
- val machine = createStateMachine { /* ... */ }
748
+ val machine = createStateMachine(scope) { /* ... */ }
737
749
println (machine.exportToPlantUml())
738
750
```
739
751
@@ -744,12 +756,13 @@ See [PlantUML nested states export sample](https://github.com/nsk90/kstatemachin
744
756
## Testing
745
757
746
758
For testing, it might be useful to check how state machine reacts on events from particular state. There
747
- are several `Testing .startFrom()` overloaded functions which allow starting the machine from a specified state:
759
+ are several `Testing .startFrom()`/ `Testing .startFromBlocking()` overloaded functions which allow starting the machine
760
+ from a specified state:
748
761
749
762
```kotlin
750
763
lateinit var state2: State
751
764
752
- val machine = createStateMachine(start = false ) {
765
+ val machine = createStateMachine(scope, start = false ) {
753
766
initialState(" state1" )
754
767
state2 = state(" state2" )
755
768
// ...
@@ -802,7 +815,8 @@ Correct - let the state machine to make decisions on an event:
802
815
machine.processEvent(SomethingHappenedEvent )
803
816
```
804
817
805
- In certain scenarios (like a `state pattern` maybe) it is fine to use events like some kind of _setState () / goToState()_
818
+ In certain scenarios (like a `state pattern` maybe) it is fine to use events like some kind of _setState () /
819
+ goToState()_
806
820
functions but in general it is wrong, as events are not commands.
807
821
808
822
## Known issues
0 commit comments