KStateMachine / kstatemachine

KStateMachine is a Kotlin DSL library for creating state machines and statecharts.
https://kstatemachine.github.io/kstatemachine/
Boost Software License 1.0
340 stars 19 forks source link

Problem using the DefaultDataState as initial state #24

Closed xdma closed 2 years ago

xdma commented 2 years ago

I ran into a problem, if I use DefaultDataState as initial state (nested), the machine falls into error, when i change it to regular state (with no data), everything works fine

My data state:

    object DialogState1 : DefaultDataState<EmergencyEventData>("Dialog_State_1")

Add as initial state (nested):

 addInitialState(EmergencyDialogStates.DialogState1) {
                dataTransition<TimeoutEvent, EmergencyEventData> {
                    targetState = EmergencyDialogStates.DialogState2
                }
            }

the data event used:

class TimeoutEvent(override val data: EmergencyEventData) : DataEvent<EmergencyEventData>

Exception when entering the state:

2021-10-21 11:09:09.962 E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
    Process: com.aloecare.hubv2.test, PID: 12584
    java.lang.IllegalStateException: ru.nsk.kstatemachine.BaseStateImpl$StartEvent@22d516f does not contain data required by DialogState1(name=Dialog_State_1)
        at ru.nsk.kstatemachine.DefaultDataState.onDoEnter(DefaultDataState.kt:19)
        at ru.nsk.kstatemachine.BaseStateImpl.doEnter(DefaultDataState.kt:116)
        at ru.nsk.kstatemachine.BaseStateImpl.notifyStateEntry(DefaultDataState.kt:252)
        at ru.nsk.kstatemachine.BaseStateImpl.setCurrentState(DefaultDataState.kt:241)
        at ru.nsk.kstatemachine.BaseStateImpl.recursiveEnterInitialStates(DefaultDataState.kt:178)
        at ru.nsk.kstatemachine.BaseStateImpl.recursiveEnterStatePath(DefaultDataState.kt:190)
        at ru.nsk.kstatemachine.BaseStateImpl.recursiveEnterStatePath(DefaultDataState.kt:196)
        at ru.nsk.kstatemachine.BaseStateImpl.switchToTargetState$kstatemachine(DefaultDataState.kt:272)
        at ru.nsk.kstatemachine.BaseStateImpl.doProcessEvent(DefaultDataState.kt:159)
        at ru.nsk.kstatemachine.StateMachineImpl.processEvent(StateMachineImpl.kt:80)
        at com.aloecare.hubv2.domain.flow.emergency.statemachine.EmergencyStateMachine.processEvent(EmergencyStateMachine.kt:209)
        at com.aloecare.hubv2.domain.base.StateMachineInterface$DefaultImpls.processEvent$default(StateMachineInterface.kt:13)
        at com.aloecare.hubv2.emergency.viewmodel.EmergencyViewModel$processEmergencyEvent$1.invokeSuspend(EmergencyViewModel.kt:450)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
nsk90 commented 2 years ago

Hi! This is intended behavior. DataState means that it is going to receive its data from Event that triggers this state. Initial state is entered implicitly when machine is started, so there is no data for it and this exception is thrown.

If you want just to store some data within your State I recommend subclassing State like this:

class SubclassState : DefaultState() {
    val dataField = 0
}

Or please provide some info about your use case and what you want to archive having DataState as initial one.

nsk90 commented 2 years ago

As I is can see from your code sample EmergencyDialogStates.DialogState2 should be DefaultDataState<EmergencyEventData> as you have a dataTransition<TimeoutEvent, EmergencyEventData> that leads to this state.

Dima-Shostak commented 2 years ago

Thank you, in the end, I solved the problem differently, in my case I need the original data to go through all internal nested states (DialogState1...) and be passed to the next states, so I accessed the original data using parent.data and this is enough at the moment

Dima-Shostak commented 2 years ago

Hi! This is intended behavior. DataState means that it is going to receive its data from Event that triggers this state. Initial state is entered implicitly when machine is started, so there is no data for it and this exception is thrown.

If you want just to store some data within your State I recommend subclassing State like this:

class SubclassState : DefaultState() {
    val dataField = 0
}

Or please provide some info about your use case and what you want to archive having DataState as initial one.

yes, I got you, but as I said, this is not quite an initial state, but a nested initial state and I need the data from the data event that leads to the nested states to start, in any case, I solved the problem differently, thank you for the answers, in general I really like the library!

if you need some more info, let me know

nsk90 commented 2 years ago

I think you've chosen the right solution. Thank you!