Custom getter/setter for properties created by type params

You can use a backing property in this situation:

data class C(private val _propertyWithSetter: String) {
    val propertyWithSetter: String
        get() = _propertyWithSetter
        set(value) {
            /* execute setter logic */
            _propertyWithSetter = value
        }
}
7 Likes

Thanks, I think that should go in the wiki :slight_smile:

2 Likes

But the name of constructor argument starts with “_” is quite a problem. (eg. named argument call, copy, …)
And if we have many properties many boilerplate will made.

I think there should be a better way for ‘private set applied to constructor var’.

The compiler is actually smart enough to do the right thing for the following code:

class person(name:string) {
    var name:String = name
      private set

  // Rest of the class
}

I Agree. Clean constructor and property expression.

But not for data classes.

I ran into the exact same problem.

I’m facing something very similar, in which I need to modify a parameter on its initial construction, and would prefer to use a data class. In particular, I am writing a Direction class for a three-dimensional unit vector using the Apache Commons Math library Vector3D; the norm of the vector should always be one. What I wish I could write is something like:

data class Direction(val vector: Vector3D set(value) { field = vector.normalize() } ) { ... }

As it is I am not using a data class, and thus am re-implementing my own equals(), hash(), …

4 Likes

I have similar problem. Firebase cant parse my data class completly. It can parse all params except for one var is_captain:String? = null

I tried to write custom get and set as mentioned above
data class Member(var _is_captain: String? = null) {
{
var is_captain: String?
get() = _is_captain
set(value) {
_is_captain = value
}
}

but it still throws ClassMapper: No setter/field for is_captain found

Hi! I found one behavior I don’t understand:

class User( nameParam: String ) {
    var name : String = nameParam  // why is nameParam saved directly into name.field here ?!?
        set(value) {
            println("Set is invoked")
            field = value.toUpperCase() + "!!!"
        }
}

fun main(args: Array<String>) {
    val ivan = User("Ivan")  // <<- setter is not invoked here!
    println(ivan.name)

    ivan.name = "Ivan" // <<- but now it is invoked 
    println(ivan.name)
}

Result of execution I expect:

Set is invoked
IVAN!!!
Set is invoked
IVAN!!!

BUT I SEE THIS:

Ivan
Set is invoked
IVAN!!! 

(tested on https://try.kotlinlang.org on all available Kotlin’s versions)

Yes, that’s correct. The initializer of a property is stored directly into a backing field, without invoking the setter.

2 Likes

Thank you for explanation. This behavior is not very clear though. I guess you use it to initialize internal field, right?
So, to achieve my aim I want to use init {} block:

class User(nameParam: String) {
  var name : String = <init_value>
     set(value) { ... }
  init {
    name = nameParam
  }
}
1 Like

It does in fact say in the documentation that the initializer value is written directly to the backing field.

Consequently, if you do want the setter to be called, you have to do this in an init block as you’re now doing.

However, an unsatisfactory aspect of doing this is that you still need to provide an initial value for the backing field which is therefore, in effect, being initialized twice. You can’t, of course, use lateinit if there’s a custom setter.

Whilst the choice of initial value isn’t really a problem for primitives or String (you can always use "" for the latter), it could be a problem for other types and you may therefore have to resort to making the property nullable.

I’m not aware of any way to avoid this double initialization.

1 Like

What about the lazy delegate?

Can’t you have a proxy function like this:

class User( nameParam: String ) {
    var name : String = transformValue(nameParam)
        set(value) {
            field = transformValue(value)
        }

}

private fun transformValue(value: String): String {
    println("Set is invoked")
    return value.toUpperCase() + "!!!"
}

It might not be as elegant, but I don’t see why it wouldn’t work. It can of course be a method as well.

1 Like

@tieskedh

Although there are some similarities, the lazy delegate is really trying to solve a different problem to what we have here.

@ilogico

Whilst your approach should work, I think it might be a case of the cure being worse than the disease!

It dawned on me after making my previous post that, if you’re going to call the setter in init, then you might as well always initialize the property to exactly the same value. So there isn’t really a problem choosing a suitable initial value even for non-primitive types.

The double initialization problem remains (as, frankly, it does in many other OO languages) though at the end of the day it’s not going to cost us many clock cycles :slight_smile:

If you want to use a data class you can use this workaround

data class Direction(var vector_: Vector3D) {
    init {
        vector_ = vector_.normalize()
    }

    val vector = vector_
}

not the best but the data class relies on the modified version of your vector. The name in the constructor is slightly different but you can’t access vector in the constructor so no error can be made and you don’t lose the power of the data class (hash and equals) nor the immutability

1 Like

This example is terrible. Firstly vector is not updated when you change vector_. Secondly you shouldn’t declare properties with backing fields in data classes except in the primary constructor.
If you really need something like this use

data class Foo(private var _test: Vec3) {
    var test: Vec3
        get() = _test
        set(v) {
            _test = transform(v)
        }
    init {
        _test = transform(test)
    }
}

sorry I forgot the private in the constructor in my example.
data class Direction(private var vector_: Vector3D) {
Adding the private in my case shows the case of an immutable data class with custom transformers whereas yours shows a mutable data class with custom transformers.
Thanks for calling my previous example terrible

Yeah, well :blush: always happy to help :wink: Maybe “terribel” is somewhat of an overreaction but reopening a topic after a year to add an example which is wrong is, well :smile:

1 Like

i am new to Kotlin and had no one to guide me , please help me with this error
i am really stuck because of this