I'm following a course in kotlin and they speak about how to make a special setter but I don't know how to make it working here is my code :
class Course (val id:Int, title:String, var duree:Int, var state:Boolean){
var title:String = title
get(){return field}
set(value){field = "my awesome" + value}
}
fun main(args: Array<String>) {
var myCourse:Course = Course (0, "stuff", 50, true)
println(myCourse.title)
}
but it keep output 'stuff' instead of 'my awesome stuff'
var myCourse:Course = Course(0, "stuff", 50, true)
With the above line of code, the Course object is initialized with the constructor. Here the setter is not called, hence it prints stuff and not my awesome stuff. The setter would get called only if you use
myCourse.title = "stuff"
If you want the setter to be called on initialization, you need to set the value of title in an init block as below
class Course(val id: Int, title: String, var duree: Int, var state: Boolean) {
var title: String = title
set(value) {
field = "my awesome $value"
}
init {
this.title = title
}
}
Or, you can drop the custom setter and set the value of title with your custom value in the init block itself
class Course(val id: Int, var title: String, var duree: Int, var state: Boolean) {
init {
this.title = "my awesome ${this.title}"
}
}
The custom setter is only used when you explicitly set the value. The custom setter is not used when you initialize the backing field at the declaration site using = title.
If you want the custom setter to be applied using the initial value, you can add an init block to your class:
init {
this.title = title
}
I was able to get you example working applying a little change in your code:
class Course (val id:Int, __title:String, var duree:Int, var state:Boolean){
var title:String = ""
get() { return field}
set(value){field = "my awesome" + value}
init {
title = __title
}
}
The difference seems to be the explicit assignment title = __title, that forces the usage of the custom setter...
Related
I just start JavaFx and am a bit stuck with TableView, it shows very long string representation of each column like:
StringProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:name, value: big]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:sheet_long, value: 5000]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:sheet_short, value: 3000]
while I am expecting only "big", "5000", "3000" to appear in the cells.
Here is my model:
object PlcSimModel {
class ErpSheet {
val name = SimpleStringProperty(this, "name")
val sheet_long = SimpleIntegerProperty(this, "sheet_long")
val sheet_short = SimpleIntegerProperty(this, "sheet_short")
}
val erpSheets = ArrayList<ErpSheet>()
}
The fxml:
<VBox alignment="CENTER" prefHeight="562.0" prefWidth="812.0" spacing="20.0"
xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.plcsim2.PlcSimController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<TableView fx:id="table_1" prefHeight="400.0" prefWidth="200.0">
</TableView>
<Button onAction="#onHelloButtonClick" text="Hello!" />
</VBox>
And finally the controller:
#FXML
private fun onHelloButtonClick() {
val rs = DB.populateSql("select name, sheet_long, sheet_short from erp_sheet")
PlcSimModel.erpSheets.clear()
if (rs != null) {
while (rs.next()) {
val sheet = PlcSimModel.ErpSheet()
sheet.name.set(rs.getString("name"))
sheet.sheet_long.set(rs.getInt("sheet_long"))
sheet.sheet_short.set(rs.getInt("sheet_short"))
PlcSimModel.erpSheets.add(sheet)
}
}
table_1.columns.clear()
val col0 = TableColumn<PlcSimModel.ErpSheet, String>("name")
col0.cellValueFactory = PropertyValueFactory("name")
table_1.columns.add(col0)
val col1 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_long")
col1.cellValueFactory = PropertyValueFactory("sheet_long")
table_1.columns.add(col1)
val col2 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_short")
col2.cellValueFactory = PropertyValueFactory("sheet_short")
table_1.columns.add(col2)
table_1.items = FXCollections.observableArrayList(PlcSimModel.erpSheets)
}
It seems controller is good, it is able to get the values from database and add rows to TableView, but why TableView shows Property object's string representation, instead of just show the value?
Thanks a lot!
JavaFX Properties
When a class exposes a JavaFX property, it should adhere to the following pattern:
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
public class Foo {
// a field holding the property
private final StringProperty name = new SimpleStringProperty(this, "name");
// a setter method (but ONLY if the property is writable)
public final void setName(String name) {
this.name.set(name);
}
// a getter method
public final String getName() {
return name.get();
}
// a "property getter" method
public final StringProperty nameProperty() {
return name;
}
}
Notice that the name of the property is name, and how that is used in the names of the getter, setter, and property getter methods. The method names must follow that format.
The PropertyValueFactory class uses reflection to get the needed property. It relies on the method naming pattern described above. Your ErpSheet class does not follow the above pattern. The implicit getter methods (not property getter methods) return the property objects, not the values of the properties.
Kotlin & JavaFX Properties
Kotlin does not work especially well with JavaFX properties. You need to create two Kotlin properties, one for the JavaFX property object, and the other as a delegate (manually or via the by keyword) for the JavaFX property's value.
Here is an example:
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
class Person(name: String = "", age: Int = 0) {
#get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
#get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
You can see, for instance, that the name Kotlin property delegates its getter and setter to the nameProperty Kotlin property.
The #get:JvmName("nameProperty") annotation is necessary for Kotlin to generate the correct "property getter" method on the Java side (the JVM byte-code). Without that annotation, the getter would be named getNameProperty(), which does not match the pattern for JavaFX properties. You can get away with not using the annotation if you never plan to use your Kotlin code from Java, or use any class that relies on reflection (e.g., PropertyValueFactory) to get the property.
See the Kotlin documentation on delegated properties if you want to use the by keyword instead of manually writing the getter and setter (e.g., var name: String by nameProperty). You can write extension functions for ObservableValue / WritableValue (and ObservableIntegerValue / WritableIntegerValue, etc.) to implement this.
Runnable Example
Here is a runnable example using the above Person class. It also periodically increments the age of each Person so you can see that the TableView is observing the model items.
import javafx.animation.PauseTransition
import javafx.application.Application
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.scene.Scene
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import javafx.util.Duration
fun main(args: Array<String>) = Application.launch(App::class.java, *args)
class App : Application() {
override fun start(primaryStage: Stage) {
val table = TableView<Person>()
table.columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
table.items.addAll(
Person("John Doe", 35),
Person("Jane Doe", 42)
)
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
primaryStage.scene = Scene(table, 600.0, 400.0)
primaryStage.show()
PauseTransition(Duration.seconds(1.0)).apply {
setOnFinished {
println("Incrementing age of each person...")
table.items.forEach { person -> person.age += 1 }
playFromStart()
}
play()
}
}
}
class Person(name: String = "", age: Int = 0) {
#get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
#get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
Avoid PropertyValueFactory
With all that said, you should avoid using PropertyValueFactory, whether you're writing your application in Java or Kotlin. It was added when lambda expressions were not yet part of Java to help developers avoid writing verbose anonymous classes everywhere. However, it has two disadvantages: it relies on reflection and, more importantly, you lose compile-time validations (e.g., whether the property actually exists).
You should replace uses of PropertyValueFactory with lambdas. For example, from the above code, replace:
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
With:
val nameCol = TableColumn<Person, String>("Name")
nameCol.setCellValueFactory { it.value.nameProperty }
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.setCellValueFactory { it.value.ageProperty }
table.columns += ageCol
Now I know PropertyValueFactory uses reflection to find the property. I thought it is the key defined to IntegerProperty or StringProperty. So simply changing the model class to following fixed the problem:
class ErpSheet {
var name = ""
var sheet_long = 0
var sheet_short = 0
}
The member variable name is the key to PropertyVaueFactory.
I got this (contrived) sample from Packt's "Programming Kotlin" on using secondary constructor with inheritance.
Edit: from the answer it is clear that the issue is about backing field. But the book did not introduced that idea, just with the wrong example.
open class Payment(val amount: Int)
class ChequePayment : Payment {
constructor(amount: Int, name: String, bankId: String) : super(amount) {
this.name = name
this.bankId = bankId
}
var name: String
get() = this.name
var bankId: String
get() = this.bankId
}
val c = ChequePayment(3, "me", "ABC")
println("${c} ${c.amount} ${c.name}")
When I run it this error is shown.
$ kotlinc -script class.kts 2>&1 | more
java.lang.StackOverflowError
at Class$ChequePayment.getName(class.kts:10)
at Class$ChequePayment.getName(class.kts:10)
at Class$ChequePayment.getName(class.kts:10)
Line 10 does seems to be a infinite recursion, how to solve it?
You have a recursion in your code:
class ChequePayment : Payment {
constructor(amount: Int, name: String, bankId: String) : super(amount) {
this.name = name
this.bankId = bankId
}
var name: String
get() = this.name // recursion: will invoke getter of name (itself)
var bankId: String
get() = this.bankId // recursion: will invoke getter of bankId (itself)
}
If you don't need custom logic for your getter, just leave your properties like this:
var name: String
var bankId: String
They will have a default getter, which does nothing more than returning the value of the backing field.
Note: The code as it is can/should be refactored to this:
class ChequePayment(amount: Int, var name: String, var bankId: String) : Payment(amount) {
// ...
}
This uses the primary constructor and is much less redundant.
To access the backing field you have to use the keyword field instead of this.name see https://kotlinlang.org/docs/reference/properties.html#backing-fields
this.name references the getter, which references this.name which is an infinite recursion, as you already noted. In code:
var name: String
get() = field
var bankId: String
get() = field
Side note: Android Studio and Idea will complain rightfully that you don't need a getter in this case. So you can simplify even more:
var name: String
var bankId: String
I am trying to find a solution for a nice kotlin data class solution. I have already this:
data class Object(
var classMember: Boolean,
var otherClassMember: Boolean,
var example: Int = 0) {
fun set(block: Object.() -> kotlin.Unit): Object {
val copiedObject = this.copy()
copiedObject.apply {
block()
}
return copiedObject
}
fun touch(block: Object.() -> kotlin.Unit): Object {
return this.set {
classMember = true
otherClassMember = false
block() }
}
}
val test = Object(true,true,1)
val changedTest = test.touch { example = 2 }
the result of this method is that the changedTest object has classMember = true, otherClassMember = false and example = 2
The problem with this solution is, the class properties are not immutable with var declaration. Does somebody have an idea how to optimize my methods to change var to val?
val says that a variable can't change it's value after initialization at the definition point. Kotlin's generated copy method does not modify an existing copy after construction: this method actually uses retrieved values from an object, replaces these values with ones that provided in copy method (if any), and after that just constructs a new object using these values.
So, it is not possible to perform such an optimization if you are going to change object's state after construction.
If I understood what you want correctly, you can do
data class Object(
val classMember: Boolean,
val otherClassMember: Boolean,
val example: Int = 0) {
fun touch(example: Int = this.example): Object {
return copy(
classMember = true,
otherClassMember = false,
example = example)
}
}
val test = Object(true,true,1)
val changedTest = test.touch(example = 2)
Though you need to repeat parameters other than classMember and otherClassMember but without reflection you can't do better.
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"
}
}
Kotlin auto-generates it's getters and settings, but I never refer to them? Also, what is the correct way to write a custom getter/setter in Kotlin? When I say myObj.myVar = 99 I feel like myVar is a public field of myObj that I'm accessing directly? What is actually happening here?
This has been answered in a few places, but I thought that I would share a concrete example for people transitioning to Kotlin from Java/C#/C/C++, and who had the same question that I did:
I was having difficulty in understanding how getters and setters worked in Kotlin, especially as they were never explicitly called (as they are in Java). Because of this, I was feeling uncomfortable, as it looked like we were just directly referring to the vars/vals as fields. So I set out a little experiment to demonstrate that this is not the case, and that in fact it is the implicit (auto-generated) or explicit getter/setter that is called in Kotlin when you access a variable/value. The difference is, you don't explicitly ask for the default getter/setter.
From the documentation - the full syntax for declaring a property is:
var <propertyName>: <PropertyType> [= <property_initializer>]
[<getter>]
[<setter>]
And my example is
class modifiersEg {
/** this will not compile unless:
* - we assign a default here
* - init it in the (or all, if multiple) constructor
* - insert the lateinit keyword */
var someNum: Int?
var someStr0: String = "hello"
var someStr1: String = "hello"
get() = field // field is actually this.someStr1, and 'this' is your class/obj instance
set(value) { field = value }
// kotlin actually creates the same setters and getters for someStr0
// as we explicitly created for someStr1
var someStr2: String? = "inital val"
set(value) { field = "ignore you" }
var someStr3: String = "inital val"
get() = "you'll never know what this var actually contains"
init {
someNum = 0
println(someStr2) // should print "inital val"
someStr2 = "blah blah blah"
println(someStr2) // should print "ignore you"
println(someStr3) // should print "you'll never know what this var actually contains"
}
I hope that helps to bring it all together for some others?
Here are some real world examples of custom getters and setters. You can see more here.
// Custom getter example
val friendlyDescription get(): String {
val isNeighborhood = district != null
var description = if (isNeighborhood) "Neighborhood" else "City"
description += " in"
if (isNeighborhood) {
description += " $city,"
}
province?.let {
if (it.isNotEmpty()) {
description += " $it,"
}
}
description += " $country"
return description
}
print(myLocation.friendlyDescription) // "Neighborhood in Denver, Colorado, United States"
// Custom setter example
enum class SearchResultType {
HISTORY, SAVED, BASIC
}
private lateinit var resultTypeString: String
var resultType: SearchResultType
get() {
return enumValueOf(resultTypeString)
}
set(value) {
resultTypeString = value.toString()
}
result.resultType = SearchResultType.HISTORY
print(result.resultTypeString) // "HISTORY"