Im trying to implement state machine with some complicated business logic
I'm holding a list of objects that I need to iterate and send each object to the state machine
sealed class State {
object PendingForAction: State()
object ActionFailed : State()
object ActionCompleted : State()
object PendingNextAction : State()
}
sealed class Event() {
object OnPendingForAction : Event()
object OnActionFailed : Event()
object OnActionCompleted : Event()
}
sealed class SideEffect() {
object UpdateStatusPendingForAction : SideEffect()
object UpdateStatusActionFailed : SideEffect()
object UpdateStatusCompleted : SideEffect()
}
#Component
class StateMachine #Autowired constructor(
) {
#Autowired
lateinit var repository: SomeRepository
val stateMachine = StateMachine.create<State, Event, SideEffect> {
initialState(State.PendingForAction)
state<State.PendingForAction> {
on<Event.OnActionFailed> {
transitionTo(State.ActionFailed, SideEffect.UpdateStatusActionFailed)
}
on<Event.ActionCompleted> {
transitionTo(State.PendingNextAction, SideEffect.UpdateStatusCompleted)
}
// so on with next actions
}
onTransition() {
val validTransition = it as? StateMachine.Transition.Valid ?: return#onTransition
when (validTransition.sideEffect) {
SideEffect.UpdateStatusPendingForAction -> {
//here i want to do complicated code that use the data object from the list
//but i can't access it
}
// and so on ...
}
}
}
}
now trigger somehow the state machine with my data:
val data = listOf(obj,obj,obj...)
// each object is in a different state and I want it to be generic enough to know the
state and perform the transition automatically
data.forEach{
stateMachine.transition(it)
}
please note that transition function only accept sideEffect but i want it to accept the object and it's state
Related
I have an app that uses Kotlin with an MVVM approach. My goal here is to send the state (SUCCESS, FAIL, ERROR, EXCEPTION.VM_INSERT...) from View Model to activity. In my activity, I observe the state and based on that I will inform the user about the state. The SUCCESS, FAIL, ERROR state works as expected, unfortunately, I struggle to find out how show in Activity the exception message send by EXCEPTION.VM_INSERT.
ViewModel State:
sealed class ViewModelState {
sealed class EXCEPTION : ViewModelState() {
class VM_INSERT(val exception: String) : ViewModelState()
class VM_UPDATE(val exception: String) : ViewModelState()
class VM_DELETE(val exception: String) : ViewModelState()
class VM_QUERY(val exception: String) : ViewModelState()
}
object SUCCESS : ViewModelState()
object FAIL : ViewModelState()
object ERROR : ViewModelState()
}
ViewModel:
...
private val _insertWordStatus = MutableLiveData<ViewModelState>()
val insertWordStatus: LiveData<ViewModelState> = _insertWordStatus
fun insertWord(word: Word) = viewModelScope.launch {
try {
val insertedRowId = repository.insertWord(word)
if (insertedRowId > -1) {
_insertWordStatus.value = ViewModelState.SUCCESS
} else {
_insertWordStatus.value = ViewModelState.FAIL
}
} catch (ex: Exception) {
_insertWordStatus.value = ViewModelState.ERROR
_insertWordStatus.value =
ex.localizedMessage.toString()?.let { ViewModelState.EXCEPTION.VM_INSERT(it) }
}
...
Activity:
...
wordViewModel.insertWordStatus.observe(this, Observer { viewModelState ->
if (viewModelState == ViewModelState.SUCCESS ) {
} else if (viewModelState == ViewModelState.FAIL) {
} else if (viewModelState == ViewModelState.ERROR) {
} else {
}
})
...
You forgot to make all the intended children of EXCEPTION subclasses of EXCEPTION. Also, you should move their shared property into the superclass so you take advantage of inheritance to, for example, log the error without having to check the specific type of EXCEPTION.
sealed class EXCEPTION(val exception: String) : ViewModelState() {
class VM_INSERT(exception: String) : EXCEPTION(exception)
class VM_UPDATE(exception: String) : EXCEPTION(exception)
class VM_DELETE(exception: String) : EXCEPTION(exception)
class VM_QUERY(exception: String) : EXCEPTION(exception)
}
In my opinion, you should refactor it as follows. I don't think the complexity of a sealed class is justified if every one of its children has identical class structure. That's really fighting against the basic principles of object oriented programming. Example:
class EXCEPTION(val exception: String, val type: EXCEPTION.Type) : ViewModelState() {
enum class Type { VM_INSERT, VM_UPDATE, VM_DELETE, VM_QUERY }
}
By the way, you should look up how to use when statements in Kotlin. It is much cleaner than a chain of else ifs when they're all checking against the same argument. Example:
wordViewModel.insertWordStatus.observe(this) { viewModelState ->
when(viewModelState) {
ViewModelState.SUCCESS - > {
}
ViewModelState.FAIL -> {
}
ViewModelState.ERROR -> {
}
ViewModelState.EXCEPTION -> {
}
}
}
Currently I have three enum classes that represents states in my state machine and one to display operations
interface State
enum class OperationState : State {
InProgress,
Finished,
Error
}
enum class FirstState : State {
//some states
}
enum class NextState: State {
//some states
}
enum class LastState: State {
//some states
}
In my service I have:
when (state) {
is FirstState -> {
//do something
changeFirstStateOperationState(state)
}
is NextState -> {
//do something
changeNextStateOperationState(state)
}
is LastState -> {
//do something
changeLastStateOperationState(state)
}
}
private fun changeFirstStateOperationState(state: FirstState){
when(state){
FirstState.A -> OperationState.Error
listOf(FirstState.B, FirstState.C) -> OperationState.InProgress
FirstState.D -> OperationState.Finished
}
}
I would like change my current implmenetation to sealed classes.
I tried something like:
sealed class State {
sealed class OperationState : State() {
sealed class FirstState : OperationState() {
object A: FirstState()
object B: FirstState()
object C: FirstState()
object D: FirstState()
}
sealed class NextState:OperationState(){ ... }
sealed class LastState:OperationState(){ ... }
}
}
but it doesn't work...
Is there any way, using the sealed class, to know what is current OperationStatus without mapping State to it?
This works for me
sealed class State {
sealed class OperationState : State() {
sealed class FirstState : OperationState() {
object A: FirstState()
object B: FirstState()
object C: FirstState()
object D: FirstState()
}
sealed class NextState:OperationState(){ }
sealed class LastState:OperationState(){ }
}
}
fun main() {
checkState(State.OperationState.FirstState.A)
}
fun checkState(state: State) {
when(state) {
is State.OperationState.FirstState.A -> "it's A"
is State.OperationState.FirstState -> "it's another FirstState"
is State.OperationState.NextState -> "it's a NextState"
is State.OperationState.LastState -> "LastState"
}.run(::println)
}
Just note that you have to specify the entire type name (State.OperationState...) unless you do an import (probably recommended if you're nesting this much), and also you have to use is in the when matcher, because you're comparing types. You don't need it for the first one (FirstState.A) since you're comparing to a specific singleton object, so it's up to you how you want it to look
We are planning to implement some behaviour control in our
CordApp, for testing purposes. Is that possible to create a
M(X)Bean, accessible via JMX, which is going to change some
internal flags in our CordApp ? If this is not a good design
choice, please inform the best practice to follow.
Basically, we have a set of flags, like these:
abstract class BaseFlow() : FlowLogic<SignedTransaction>() {
var flagBehaviourOne : Boolean = true
var flagBehaviourTwo : Boolean = true
var flagBehaviourThree: Boolean = true
var flagBehaviourFour : Boolean = true
...
}
then, in some implementing class, we have something like this:
object SomeFlow {
#InitiatingFlow
class Initiator(private val destinatario: Party,
private val parameter: StateObject,
private val isAnonymous: Boolean = false,
private val pointer: Any) : BaseFlow() {
...
#Suspendable
override fun call(): SignedTransaction {
if (flagBehaviourOne || flagBehaviorTwo) {
// enforce some specific behaviour
}
...
} // end of SomeFlow.Initiator
...
} // end of SomeFlow
I have (partially) solved my problem.
I have added a new object class, along with its jmx interface :
package vfalcao.example.jmx
import java.lang.management.ManagementFactory
import javax.management.MXBean
import javax.management.ObjectName
#MXBean
interface BehaviourControlMXBean {
fun setBehaviourOne(newValue: String)
fun isBehaviourOne() : String
...
// other "behaviours" ommited for brevity
}
object BehaviourControl : BehaviourControlMXBean {
// internal data
...
init {
val objectName = ObjectName("vfalcao.example.jmx:type=BehaviourControl,name=def")
val platformMBeanServer = ManagementFactory.getPlatformMBeanServer()
platformMBeanServer.registerMBean(this, objectName)
}
}
then, in my BaseFlow class:
abstract class BaseFlow() : FlowLogic<SignedTransaction>() {
companion object {
...
init {
println("${BehaviourControl}")
}
...
fun test() {
var behaviour1 = ((BehaviourControl.props["behaviour1"] as String).toBoolean())
if (behaviour1) {
// do something controlled by behaviour1
}
}
}
...
}
I am trying to understand how to hide a base constructor parameter in a subclass in kotlin. How do you put a facade over a base constructor? This doesn't work:
import com.android.volley.Request
import com.android.volley.Response
class MyCustomRequest(url: String)
: Request<String>(Request.Method.POST, url, hiddenListener) {
private fun hiddenListener() = Response.ErrorListener {
/* super secret listener */
}
...
}
I think I understand the problem:
During construction of a new instance of a derived class, the base
class initialization is done as the first step (preceded only by
evaluation of the arguments for the base class constructor) and thus
happens before the initialization logic of the derived class is run.
I'm trying to solve this problem for Volley, where I need my custom request to be be a Request so that it can be passed into a RequestQueue. It would be easier of RequestQueue took in some kind of interface but since it doesn't I have to subclass. There are other ways I can hide these complexities from the caller, but this limitation has come up for me other times in Kotlin and I'm not sure how to solve it.
I am not familiar with volley but I tried to come up with an example that should give you some insight how to solve your problem. What you can do is use a companion object:
interface MyListener {
fun handleEvent()
}
open class Base<T>(anything: Any, val listener: MyListener) { // this would be your Request class
fun onSomeEvent() {
listener.handleEvent()
}
}
class Derived(anything: Any) : Base<Any>(anything, hiddenListener) { // this would be your MyCustomRequest class
private companion object {
private val hiddenListener = object : MyListener {
override fun handleEvent() {
// do secret stuff here
}
}
}
}
So if you apply this to your problem, the result should look something like this:
class MyCustomRequest(url: String)
: Request<String>(Request.Method.POST, url, hiddenListener) {
private companion object {
private val hiddenListener = Response.ErrorListener {
/* super secret listener */
}
}
...
}
A different way would be to use a decorator, create your Request withing that decorator and just delegate the calls to it:
class Decorator(anything: Any) {
private var inner: Base<Any>
private val hiddenListener: MyListener = object : MyListener {
override fun handleEvent() { }
}
init {
inner = Base(anything, hiddenListener)
}
}
And once again for your example that would look like this:
class MyCustomRequest(url: String) {
private var inner: Request<String>
private val hiddenListener = Response.ErrorListener {
/* super secret listener */
}
init {
inner = Request<String>(Request.Method.POST, url, hiddenListener)
}
...
}
I have a tornadoFX application following the MVVM pattern with the model:
data class Person (
val name: String,
val cars: List<Car>
)
data class Car (
val brand: String,
val model: String
)
The application defines the following view:
There is a list-view that lists all persons. Besides the listView is a details-view with a text-field for the person´s name and a table-view for the person´s cars.
A double click on a car entry in the table opens a dialog, in which one can edit the car´s properties.
I want, that if I open the car-details and edit an entry, the changes will be reflected in the table-view. Since i can´t alter the Car-model (which is an immutable type) by adding fx-properties, i came up with the following view-model:
class PersonViewModel(): ItemViewModel<Person> {
val name = bind(Person::name)
val cars = bind { SimpleListProperty<CarViewModel>(item?.cars?.map{CarViewModel(it)}?.observable()) }
override fun onCommit {
// create new person based on ViewModel and store it
}
}
class CarViewModel(item: Car): ItemViewModel<Car> {
val brand = bind(Car::name)
val model = bind(Car::model)
init {
this.item = item
}
}
This way, if double-click on a car-entry in the table-view and open the car-detail-view, an update on the car will be directly reflected in the table-view.
My Problem here is, that I can´t find a way to bind the dirty properties of all my CarViewModels in the table to the PersonViewModel. So if I change a car, the PersonViewModel is not marked as dirty.
Is there a way to bind the dirty-properties of PersonViewModel and CarViewModel? (And also rebind them, if another person is selected).
Or is there even a better way to define my view-models?
I've made a change to the framework to allow ViewModel bindings towards lists to observe ListChange events. This enables you to trigger the dirty state of a list property by altering the list somehow. Merely changing a property inside an item in the list will not trigger it, so in the following example I just get the index of the Car before committing, and reassigning the Car to the same index. This will trigger a ListChange event, which the framework now listens for.
The important action happens in the Car dialog save function:
button("Save").action {
val index = person.cars.indexOf(car.item)
car.commit {
person.cars[index] = car.item
close()
}
}
The index of the car is recorded before the values are committed (to make sure that equals/hashCode matches the same entry), then the newly committed item is inserted in the same index, thus triggering a change event on the list.
Here is a complete example, using mutable JavaFX properties, since they are the idiomatic JavaFX way. You can pretty easily adapt it to using immutable items, or use wrappers.
class Person(name: String, cars: List<Car>) {
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
val carsProperty = SimpleListProperty<Car>(FXCollections.observableArrayList(cars))
var cars by carsProperty
}
class PersonModel : ItemViewModel<Person>() {
val name = bind(Person::nameProperty)
val cars: SimpleListProperty<Car> = bind(Person::carsProperty)
}
class Car(brand: String, model: String) {
val brandProperty = SimpleStringProperty(brand)
var brand by brandProperty
val modelProperty = SimpleStringProperty(model)
var model by modelProperty
}
class CarModel(car: Car? = null) : ItemViewModel<Car>(car) {
val brand = bind(Car::brandProperty)
val model = bind(Car::modelProperty)
}
class DataController : Controller() {
val people = FXCollections.observableArrayList<Person>()
init {
people.add(
Person("Person 1", listOf(Car("BMW", "M3"), Car("Ford", "Fiesta")))
)
}
}
class PersonMainView : View() {
val data: DataController by inject()
val selectedPerson: PersonModel by inject()
override val root = borderpane {
center {
tableview(data.people) {
column("Name", Person::nameProperty)
bindSelected(selectedPerson)
}
}
right(PersonEditor::class)
}
}
class PersonEditor : View() {
val person: PersonModel by inject()
val selectedCar : CarModel by inject()
override val root = form {
fieldset {
field("Name") {
textfield(person.name).required()
}
field("Cars") {
tableview(person.cars) {
column("Brand", Car::brandProperty)
column("Model", Car::modelProperty)
bindSelected(selectedCar)
onUserSelect(2) {
find<CarEditor>().openModal()
}
}
}
button("Save") {
enableWhen(person.dirty)
action {
person.commit()
}
}
}
}
}
class CarEditor : View() {
val car: CarModel by inject()
val person: PersonModel by inject()
override val root = form {
fieldset {
field("Brand") {
textfield(car.brand).required()
}
field("Model") {
textfield(car.model).required()
}
button("Save").action {
val index = person.cars.indexOf(car.item)
car.commit {
person.cars[index] = car.item
close()
}
}
}
}
}
The feature is available in TornadoFX 1.7.17-SNAPSHOT.