Skip to content

State Bean

State Beans are components that can be created to handle interaction with a state via the StateContext.

Creating a State Bean

State Beans are spring components defined by adding @State to a class that also implements one of the Supported State Bean types. State beans can also be defined for multiple states using @States with multiple @State annotations.

The following class (OnSomeAction) is an example of what a State Bean looks like that will be evaluated when StateContext has the current state of MyState.

@State(name = "MyState")
class OnSomeAction {
    ...
}
@States({
        @State(name = "MyState"),
        @State(name = "MyOtherState")
})
class OnSharedAction {
    ...
}

Lifecycle

State Beans are created as prototype beans and are generated each time they are needed by the StateMachine during the action loop. This means that any members on the class MUST be annotated with the Scoped Context annotations of @In, @InOut, or @Out in order to make data available to other state beans or to persist it past the invocation of the state bean.

Supported State Bean

Action Handler

Action Handler State Beans are a type State Bean that conforms to the Responding to Actions of flow control.

@State(name = "MyState")
class OnSomeAction {
    @ActionHandler
    public void onSomeAction(Action action) {
        ...
    }
}

Event Handler

Event Handler State Beans are a type of State Bean that are invoked when AppEvent are published via EventPublisher.

@State(name = "MyState")
class OnSomeEventHandler {
    @OnEvent(sources = SELF)
    public void onSomeEvent(SomeEvent event) {
        ...
    }
}

Screen Builder

Screen Builder State Beans are a type of State Bean that are invoked from the ShowScreenAfterActionLoop which is only invoked when the StateManager is not busy and the current device has no more current actions in the action loop queue. Screen Builder State Beans must implement IBuildScreen and return a non-empty Optional in order for the given screen builder to have been used. Screen Builders can also have extra filters that other State Beans do not support which is the @ActionScreenBuilder annotation which acts as a filter for the state bean and a priority order for that specific screen builder to be evaluated sooner.

Note: Screen Builders with @ActionScreenBuilder where the value attribute contains an action that was completed in the action loop will be prioritized above other state beans, this includes State beans with a higher Order of Precedence unless both beans have the @ActionScreenBuilder annotation in which case both will be ordered above other State Beans and then ordered by the standard order of precedence amongst the @ActionScreenBuilder beans.

StateContext Resolution

State Beans support multiple layers of filtering to allow for state beans to be applicable in the most appropriate case given the business need. By specifying the properties for the @State annotation a State Bean will be restricted to only be evaluated when the StateContext meets the annotations properties. For a State Bean to be considered a match all of the @State properties must match the StateContext OR be left blank.

Note: This does mean that State Beans can be defined globally and evaluated for every state.

State

A State Bean will be considered a match for a StateContext when the class of the state object matches the name attribute of the @State annotation.

With the following example the StateContext#state member is an instance of MyState only OnSomeAction would be resolved as a candidate for an appropriate State Bean to then be further evaluated for matching of an Action Handler, Event Handler, or Screen Builder.

class StateContext {
    private Object state; // = MyState
}

@State(name = "MyState")
class OnSomeAction {

}

@State(name = "OtherState")
class OnSomethingElse {

}

Flow

A State Bean will be considered a match for a StateContext when the flowConfig has a name that matches the flow attribute of the @State annotation.

With the following example the StateContext has a flowConfig with a name of MyFancyFlow only OnFlowAction would be resolved as a candidate for an appropriate State Bean to then be further evaluated for matching of an Action Handler, Event Handler, or Screen Builder.

class FlowConfig {
    String name; // = "MyFancyFlow"
}

class StateContext {
    FlowConfig flowConfig;
}

@State(flow = "MyFancyFlow")
class OnFlowAction {
    ...
}

@State(flow = "MyOtherFlow")
class OnOtherFlowAction {
    ....
}

App ID

A State Bean will be considered a match for a StateContext when the appId matches the appId attribute of the @State annotation. The StateContext will be populated with the appId of the device from its initial connection.

With the following example the StateContext has an appId of customerdisplay only CustomerDisplayOnSomeAction would be resolved as a candidate for an appropriate State Bean to then be further evaluated for matching of an Action Handler, Event Handler, or Screen Builder.

class StateContext {
    String appId; // = "customerdisplay"
}

@State(appId = "pos")
class POSOnSomeAction {
    ...
}

@State(appId = "customerdisplay")
class CustomerDisplayOnSomeAction {
    ....
}

Order of Precedence

In almost every case a state will have more than one State Bean defined. When multiple state beans are defined they will be ordered based on the specificity of the @State annotation itself then optionally based on the needs of the State Bean type further ordering followed finally be springs PriorityOrdered, Ordered, and finally the Order annotation.

When determining the order the State Beans will be evaluated based on the name, flow, and appId attributes of the @State annotation with the most precedence given to a value with a non-default value then ordered by state, flow, then appId.

Order Evaluation

  1. State, Flow, and AppId all non-default values
  2. State and Flow defined non-default values
  3. State and AppId defined non-default values
  4. State defined non-default value
  5. Flow and AppId defined non-default values
  6. Flow defined non-default value
  7. AppId defined non-default value
  8. All default values

Order Example

@State(name = "MyState", flow = "MyFlow", appId = "my-app")
class OnSomething1 {
    // Example of State Bean Definition meeting criteria 1. 
}

@State(name = "MyState", flow = "MyFlow")
class OnSomething2 {
    // Example of State Bean Definition meeting criteria 2.
}

@State(name = "MyState", appId = "my-app")
class OnSomething3 {
    // Example of State Bean Definition meeting criteria 3.
}

@State(name = "MyState")
class OnSomething4 {
    // Example of State Bean Definition meeting criteria 4.
}

@State(flow = "MyFlow", appId = "my-app")
class OnSomething5 {
    // Example of State Bean Definition meeting criteria 5.
}

@State(flow = "MyFlow")
class OnSomething6 {
    // Example of State Bean Definition meeting criteria 6.
}

@State(appId = "my-app")
class OnSomething7 {
    // Example of State Bean Definition meeting criteria 7.
}

@State
class OnSomething8 {
    // Example of State Bean Definition meeting criteria 8.
}

Conditional Base Implementations

All base implementation beans should be annotated with @ConditionalOnMissingActionHandler or @ConditionalOnMissingScreenBuilder which will evaluate if any other state bean for the given bean type (Action Handler or Screen Builder) is defined that meets the exact State Bean definition as the bean with the conditional annotation.

ConditionalOnMissingActionHandler

Given base has a State Bean defined for MyState to handle an action of SomeAction if an implementer needs to extend the state and adjust what happens for SomeAction just by creating a new State Bean of OnSomeAction with the same definition as the base it will be the chosen implementation.

So with the following action, BaseOnSomeAction will not appear in the Spring ApplicationContext but OnSomeAction will be present.

@State(name = "MyState")
@ConditionalOnMissingActionHandler
class BaseOnSomeAction {
    @ActionHandler
    public void onSomeAction(Action action) {
        ...
    }
}

@State(name = "MyState")
class OnSomeAction {
    @ActionHandler
    public void onSomeAction(Action action) {
        ...
    }
}

ConditionalOnMissingScreenBuilder

Screen Builder can be present as both a default screen matching a @State annotation or for a specific action after the action loop. The screen builder condition will prevent a screen builder from being match when the state configuration is an exact match just like the Action Handler condition but it will also check if the screen builder is annotated with @ActionScreenBuilder as an extra condition since that is considered to be a unique state bean.

Given the following example, BaseBuildMyStateScreen would not be added to the Spring ApplicationContext and instead would add BuildMyStateScreen. However, BaseBuildMyStateMyActionScreen would still be added to the ApplicationContext since it was also annotated with ActionScreenBuilder and the action name was not defined in an override screen builder.

@State(name = "MyState")
@ConditionalOnMissingScreenBuilder
class BaseBuildMyStateScreen implements IBuildScreen<MyStateUIMessage> {
    ...
}

@ActionScreenBuilder("MyAction")
@State(name = "MyState")
@ConditionalOnMissingScreenBuilder
class BaseBuildMyStateMyActionScreen implements IBuildScreen<MyActionMyStateUIMessage> {
    ...
}

@State(name = "MyState")
class BuildMyStateScreen implements IBuildScreen<MyStateUIMessage> {
    ...
}