Kotlin: Override setter - kotlin

I have an abstract class containing the following property
var items: List<I> = listOf()
set(value) {
field = value
onDataChanged()
}
In my extending class i now want to override the setter of items to do additional stuff before the above setter code is called. Is this possible, if yes, how?

You have to declare your field as open in your parent class
open var items: List<I> = listOf()
set(value) {
field = value
onDataChanged()
}
And in your child class you override it as:
override var items: List<Int>
get() = super.items
set(value) {
super.items = value
//Your code
}
In this way, you are actually creating a property without a backing field and you are just accessing to the real parent's items field.

Related

How to address a getter from inside the class in Kotlin

Consider following Kotlin-Code:
class Foo(input: Int) {
private var someField: Int = input
get() = -field
set(value) {
field = -value
}
fun bar() {
println(someField)
}
}
fun main() {
Foo(1).bar()
}
This prints -1 in the console which means that inside method bar() someField references the attribute and not the corresponding getter. Is there a way that allows me to use the get()-method as if I was referencing this field from outside?
Perhaps you could track the "raw" value separately from the negative value? Something like this:
class Foo(input: Int) {
private var _someField: Int = input
var someField: Int
get() = -_someField
set(value) {
_someField = -value
}
fun bar() {
println(someField)
}
}
Now the class internals can reference _someField to deal directly with the raw value, while outside clients can only "see" someField.

How do I make a delegate apply to all properties in a class?

I have a class, A, that needs to be marked as dirty anytime one of its properties is changed.
After reviewing the Kotlin docs, I know I need a delegate. So far I have:
abstract class CanBeDirty {
var isDirty = false
}
class A(
// properties getting set in constructor here
) : CanBeDirty {
var property1: String by DirtyDelegate()
var property2: Int by DirtyDelegate()
var property3: CustomObject by DirtyDelegate()
}
class DirtyDelegate() {
operator fun getValue(thisRef: CanBeDirty, property: KProperty<*>): Resource {
return valueOfTheProperty
}
operator fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: Any?) {
if (property != value) {
thisRef.isDirty = true
//set the value
}
else {
//don't set the value
}
}
}
I believe the lack of setting has something to do with vetoable() but the examples I see in Kotlin documentation don't really show me how to do this with a fully formed class Delegate (and I'm just not that up to speed on Kotlin syntax, honestly).
Your delegate class needs its own property to store the value it will return. And if you don't want to deal with uninitialized values, it should also have a constructor parameter for the initial value. You don't have to implement ReadWriteProperty, but it allows the IDE to autogenerate the correct signature for the two operator functions.
class DirtyDelegate<T>(initialValue: T): ReadWriteProperty<CanBeDirty, T> {
private var _value = initialValue
override fun getValue(thisRef: CanBeDirty, property: KProperty<*>): T {
return _value
}
override fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: T) {
if (_value != value) {
_value = value
thisRef.isDirty = true
}
}
}
Since this takes an initial value parameter, you have to pass it to the constructor:
class A: CanBeDirty() {
var property1: String by DirtyDelegate("")
var property2: Int by DirtyDelegate(0)
var property3: CustomObject by DirtyDelegate(CustomObject())
}
If you wanted to set an initial value based on something passed to the constructor, you could do:
class B(initialName: String): CanBeDirty() {
var name by DirtyDelegate(initialName)
}

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.

Implementing properties declared in interfaces in Kotlin

I'm new to Kotlin, so I have this interface.
interface User {
var nickName : String
}
Now I want to create a class PrivateUser that implements this interface. I have also to implement the abstract member nickName.
Via constructor it's very simple
class PrivateUser(override var nickName: String) : User
However when I try to implement member inside the class Idea generates me this code
class Button: User {
override var nickName: String
get() = TODO("not implemented")
set(value) {}
}
It's confusing to me how to implement it further.
Properties must be initialized in Kotlin. When you declare the property in the constructor, it gets initialized with whatever you pass in. If you declare it in the body, you need to define it yourself, either with a default value, or parsed from other properties.
Some examples:
class Button : User {
override var nickname = "Fred"
}
class Button(val firstName: String, val lastName: String) : User {
override var nickname = "${firstname[0]}$lastname"
}
The code generated by IDEA is useful if you want a non-default getter and/or setter, or if you want a property without a backing field (it's getter and setter calculate on the fly when accessed).
More examples:
class Button : User {
override var nickname = "Fred"
get() = if (field.isEmpty()) "N/A" else field
set(value) {
// No Tommy
field = if (value == "Tommy") "" else value
}
}
class Button(val number: Int) : User {
var id = "$number"
private set
override var nickname: String
get() {
val parts = id.split('-')
return if (parts.size > 1) parts[0] else ""
}
set(value) {
field = if (value.isEmpty()) "$number" else "$value-$number"
}
}

Accessing field of a different instance of the same class in Kotlin

Consider this Kotlin code:
var parent: T? = null
get() = if (isParent) this as T else field
set(value) { field = if (value == null) null else value.parent }
val isParent: Boolean
get() = parent == null
var description = ""
get() = if (isParent) field else parent!!.description
set(value) { if (isParent) field = value else parent!!.description = value }
Assume that isParent returns true if this instance is a parent instance. If not getParent() will return the parent instance. In Java you are allowed to access directly field of a different instance of same class like this:
String getDescription() { return getParent().description; }
void setDescription(String value) { getParent().description = value; }
(I am not saying that is a best thing to do, I simplified it for demostration). Comparing to Java, it would be nice to be able to do following:
var description = ""
get() = parent.field
set(value) { parent.field = value }
However this does not work and unfortunately it makes the code less readable. Especially if you have a lot of such variables, which are bound to this parent.
A backing field of a property can only be accessed from a getter or setter of that property, and only for the instance on which the getter or setter has been invoked. If you need to provide multiple ways to access an attribute of a class, you need to define two distinct properties, one of which has a backing field to store the data and another has a getter and setter referring to the first property.
class Foo {
var parent: Foo? = null
val parentOrSelf: Foo get() = parent ?: this
private var _description: String? = null
var description = ""
get() = parentOrSelf._description
set(value) { parentOrSelf._description = value }
}