How to use custom setter in Kotlin class constructor body

5.2k views Asked by At

I couldn't find a better title for describing how can I avoid code duplication (require expressions) in this Kotlin class:

class Person(email: String) {
    var email: String = email
        set(value) {
            require(value.trim().isNotEmpty(), { "The email cannot be blank" })
            field = value
        }

    init {
        require(email.trim().isNotEmpty(), { "The email cannot be blank" })
    }
}

In java, I would have a setter with the name validation and then I would call it from the constructor.

What is the idiomatic way of doing that in Kotlin ?

4

There are 4 answers

0
jah On

You can do it just like in java. You just have to remove the primary constructor and make a secondary constructor instead. You will have to do the assignment manually though, like in java. So it would look like this:

class Person {
    constructor(email: String) {
        this.email = email
    }

    var email: String
        set(value) {
            require(value.trim().isNotEmpty(), { "The email cannot be blank" })
            field = value
        }
}
0
Qiyu Zhang On

There's an alternative way, to use companion object and create object there, it's kind like the constructor and meets your requirement in a different way.

data class Target(
        val date: LocalDate,
        val id: Long,
        val stringId: String,
) {
    companion object {
        fun create(date: LocalDate, id: Long, stringId: String?,): Target{
            val nonNullStringId= if (stringId.isNullOrEmpty()) "" else stringId
            return Target(date, id, nonNullStringId)
        }
    }
}
2
Paul Hicks On

Define the member outside of the constructor, and invoke the setter from the init block:

class Person(initialEmail: String) { // This is just the constructor parameter.
    var email: String = "" // This is the member declaration which calls the custom setter.
        set(value) {          // This is the custom setter.
            require(value.trim().isNotEmpty(), { "The email cannot be blank" })
            field = value
        }

    init {
        // Set the property at construct time, to invoke the custom setter.
        email = initialEmail
    }
}
7
Paul Hicks On

Use a delegate. There's the observable() delegate that already exists.

class Person(initialEmail: String) { // No "var" any more.
    var email: String by Delegates.observable("") {
    _, _, newValue ->
        // This code is called every time the property is set.
        require(newValue.trim().isNotEmpty(), { "The email cannot be blank" })
    }

    init {
        // Set the property at construct time, to invoke the delegate.
        email = initialEmail
    }
}