merging different models into sealed class - kotlin

I’m writing PC app using Kotlin and TornadoFX.
I’ll show you what I have
FooModel.kt:
class FooModel(val id: Int){
constructor(foo: Foo) : this(foo.id)
}
Foo.kt:
data class Foo(val id: Int)
MainController.kt:
val FoosList: ObservableList<FooModel> = FXCollections.observableArrayList<FooModel>()
//bellow I wanna describe what I’m already doing with val above
fun fooDifferentOperationsForExample(example: Int){
example1Object = FoosList.find{ it.id == example }
example2Object.removeIf { it.id == example }
example3Object.setAll(it.map {FooModel})
}
I’m showing described model in listview and want to add objects of another kind(s) in the same list. I have a solution with sealed class, example of DifferentKindsOfInstances.kt:
sealed class DifferentKindsOfInstances{
data class Foo(val foo: FooModel): DiffrentKindsOfInstances()
data class Bar(val bar: BarModel): DiffrentKindsOfInstances()
}
let’s think BarModel is exact copy of FooModel, nevermind.
But if I wanna continue working with FooModel in a way I worked before(in MainControl) - how would the code look then?
it.foo.id doesn’t work for me

Here's a solution that references the sealed class members in two places: rendering the ListCell in cellFragment{} and in the binding of the selected sealed class item to the textfield string content.
class ItemModel1 ( val dataField1 : String )
class ItemModel2 ( val dataField2 : String )
sealed class ItemViewInstance {
data class Item1(val item1: ItemModel1): ItemViewInstance()
data class Item2(val item2: ItemModel2): ItemViewInstance()
}
class SealedClassDemoView : View("Sealed Class") {
val instances = listOf(
ItemViewInstance.Item1(ItemModel1("One")),
ItemViewInstance.Item2(ItemModel2("Two"))
).observable()
val selectedItemString = SimpleStringProperty()
override val root = vbox {
listview(instances) {
cellFormat {
when( it ) {
is ItemViewInstance.Item1 -> text = it.item1.dataField1
is ItemViewInstance.Item2 -> text = it.item2.dataField2
}
}
selectedItemString.bind(
Bindings.createStringBinding(
Callable {
val sel = selectionModel.selectedItemProperty().value
when (sel) {
is ItemViewInstance.Item1 -> sel.item1.dataField1
is ItemViewInstance.Item2 -> sel.item2.dataField2
else -> ""
}
},
selectionModel.selectedItemProperty()
)
)
}
textfield(selectedItemString)
padding = Insets(2.0)
spacing = 4.0
}
}
class SealedClassDemoApp : App(SealedClassDemoView::class)
fun main(args: Array<String>) {
launch<SealedClassDemoApp>(args)
}

Related

Kotlin annotation changing of target

I have a annotation AggregateId that could be set on method params and properties and that I will use to retrieve some id :
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
#Retention(AnnotationRetention.RUNTIME)
annotation class AggregateId
I wrote that test case :
data class Example(
#AggregateId
val id: UUID
)
class AggregateIdTests {
private val exUuid = UUID.fromString("bae30706-f949-4eb5-b091-d51a13ddc832")
#Test
fun test() {
val ex = Example(id = exUuid)
val id = resolve(ex)
Assertions.assertThat(id).isEqualTo(exUuid)
}
private fun resolve(target: Any): UUID? {
val prop = target::class.declaredMemberProperties.find {
it.findAnnotation<AggregateId>() != null
}
return prop?.getter?.call(target) as UUID?
}
}
That actually works.
But if I add this class in the code :
class TestClass {
fun aMethod(#AggregateId param: UUID) {
}
}
Suddently the AggregateId changes of target for the other class. Even though I didn't change the rest of the code. What is the explaination of this ?
(using kotlin 1.5)

Get kotlin sealed class name heirarchy

I wrote a dirty function that gives me the name structure of a sealed class heirarchy.
It sucks because its using reflection - so i'm asking if there's a better, non reflective way.
fun KClass<*>.sealedClassName(): String {
var className = simpleName
var sealedParent = superclasses.firstOrNull { it.isSealed }
while (sealedParent != null) {
className = "${sealedParent.simpleName}.$className"
sealedParent = sealedParent.superclasses.firstOrNull { it.isSealed }
}
return className ?: qualifiedName ?: jvmName
}
interface AnInterface
open class AnClass
sealed class Grandparent : AnClass(), AnInterface {
sealed class Parent : Grandparent(), AnInterface {
object Child : Parent()
}
object AuntieObject : Grandparent()
data class UncleData(val x: Int) : Grandparent()
}
#Test
fun sealedClassName() {
check(Grandparent.Parent::class, "Grandparent.Parent")
check(Grandparent.AuntieObject::class, "Grandparent.AuntieObject")
check(Grandparent.UncleData::class, "Grandparent.UncleData")
check(Grandparent.Parent.Child::class, "Grandparent.Parent.Child")
check(Unit::class, "Unit")
}

How to instantiate class in Kotlin, if the class is parameter of function?

Im trying to make universal function in Kotlin, which can instantiate every time different model classes.
Class type is a parameter, to make instance from that class and fill it with data from Json object.
fun<T> foo() {
var myModel = T()
myModel.id = 2
myModel.name = ""
}
You can use an inline reified function in combination with an interface.
With reified you can access the real class of the generic type parameter.
You still not allowed to call the constructor directly, but reflection will work.
To assign the id and name, you need to define an interface, that all of your model classes are required to implement:
interface Model {
var id: Int?
var name: String?
}
inline fun <reified T : Model> createModel() : T {
val myModel = T::class.createInstance()
myModel.id = 2
myModel.name = ""
return myModel
}
A simple example:
class TestModel() : Model {
override var id: Int? = null
override var name: String? = null
}
fun main() {
val model: TestModel = createModel()
}
You cannot use T definition itself, pass class to the function instead.
import kotlin.reflect.KClass
open class Model {
var id: Int? = null
var name: String? = null
fun say() {
println("hello.")
}
}
class MyModel: Model()
fun<T: Model> foo(type: KClass<T>): T {
val myModel = type.java.newInstance()
myModel.id = 2
myModel.name = ""
return myModel
}
val mymodel = foo(MyModel::class)
mymodel.say() // hello.

Bind dirty properties of different view-models

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.

Use a class from a list of generic interface

I am trying to implement a QueryBus. Basically, I want to register a list of QueryHandlers. Each QueryHandler implements a handle method defined by an interface. Each QueryHandler is associated to a Query. I want to be able to retrieve a QueryHandler using the Query and call handle on it.
The thing is the handle has to be generic because each QueryHandler handles a Query differently. They all take a dedicated Query and may return whatever they want.
interface Query<R>
interface QueryHandler<R, Q : Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
class ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
class GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = mapOf(
with(ListBooksQueryHandler()) {this.listenTo() to this},
with(GetBookQueryHandler()) {this.listenTo() to this}
)
val command = ListBooksQuery()
val result = queryHandlers[command::class.toString()].handle(command)
// Should print the list of BookDto
print(result)
}
I don't even know if its possible, to be honest.
UPDATE 1:
I changed the usage example in the main to show what I am really trying to do. The List was for (bad?) demonstration purpose. I want to store the QueryHandlers and retrieve them from a map.
Additional resources:
Here is what I really want to do:
https://gist.github.com/ValentinTrinque/76b7a32221884a46e657090b9ee60193
UPDATE I've read your gist and tried to come up with a solution that will provide a clean interface to the user of the QueryBusMiddleware.
Note that I used objects instead of classes for the QueryHandler implementations, which felt more natural to me (since there is only one possible entry in the map for each Query implementation).
interface Query<R>
interface QueryHandler<R, Q: Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
object ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
object GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = listOf(
ListBooksQueryHandler,
GetBookQueryHandler
)
val dispatcher: QueryBusMiddleware = QueryDispatcherMiddleware(queryHandlers)
// Calling query bus
val query = ListBooksQuery()
// Result should be List<BookDto>
val result = dispatcher.dispatch(query)
print(result)
}
interface QueryBusMiddleware {
fun <R, Q : Query<R>> dispatch(query: Q): R
}
class QueryDispatcherMiddleware constructor(handlers: List<QueryHandler<*, *>>) : QueryBusMiddleware {
private val handlers = HashMap<String, QueryHandler<*, *>>()
init {
handlers.forEach { handler -> this.handlers[handler.listenTo()] = handler }
}
override fun <R, Q : Query<R>> dispatch(query: Q): R {
val queryClass = query::class.toString()
val handler = handlers[queryClass] ?: throw Exception("No handler listen to the query: $queryClass")
return handler::class.members.find { it.name == "handle" }!!.call(handler, query) as R
}
}