Private getter and public setter for a Kotlin property
Asked Answered
C

3

73

How to make a property in Kotlin that has a private getter (or just do not have it) but has a public setter?

var status
private get

doesn't work with an error: Getter visibility must be the same as property visibility

In my case, the reason is for Java interop: I want my Java code to be able to call setStatus but not getStatus.

Crypto answered 7/7, 2016 at 10:23 Comment(5)
Just curious: Why would you want to do that? A write-only field?Dasyure
private get means using the property directly in the class, isn't it ?Joyejoyful
Yes. A write-only field. It is read from inside the class only.Chlorite
This is a good question. I am curious though about the use case of a write-but-no-read field? I can't think of a reason you would need it not to be readable. Can you share your use case?Headache
@Headache This would be a typical use case for a builder and command design patterns.Jalisajalisco
G
53

It's impossible at the moment in Kotlin to have a property with a setter that is more visible than the property. There's a language design issue in the issue tracker on this, feel free to watch/vote for it or share your use cases: https://youtrack.jetbrains.com/issue/KT-3110

Garth answered 8/7, 2016 at 14:30 Comment(1)
saying that _ in Kotlin to have a property with a setter that is more visible than the property_ is the same as saying that the getter cannot have more visibility than the getter?Maltha
R
31

In current Kotlin version (1.0.3) the only option is to have separate setter method like so:

class Test {
    private var name: String = "name"

    fun setName(name: String) {
        this.name = name
    }
}

If you wish to restrict external libraries from accessing the getter you can use internal visibility modifier allowing you to still use property syntax within the library:

class Test {
    internal var name: String = "name"
    fun setName(name: String) { this.name = name }
}

fun usage(){
    val t = Test()
    t.name = "New"
}
Razzia answered 7/7, 2016 at 10:57 Comment(1)
Just as a add-on if you plan to have Java use the code, the internal keyword eventually become public in Java worldNickolenicks
B
9

Write-only properties with compile-time errors can be achieved since Kotlin 1.0, using a workaround based on @Deprecated.

Implementation

Kotlin allows to mark functions deprecated with level ERROR, which leads to a compile-time error when called. Annotating the get accessor of a property as error-deprecated, combined with a backing field (so that private reads are still possible), achieves the desired behavior:

class WriteOnly {
    private var backing: Int = 0

    var property: Int
        @Deprecated("Property can only be written.", level = DeprecationLevel.ERROR)
        get() = throw NotImplementedError()
        set(value) { backing = value }

    val exposed get() = backing // public API
}

Usage:

val wo = WriteOnly()
wo.property = 20         // write: OK

val i: Int = wo.property // read: compile error
val j: Int = wo.exposed  // read value through other property

The compile error is quite helpful, too:

Using 'getter for property: Int' is an error. Property can only be written.


Use cases

  1. The main use case are obviously APIs that allow properties to be written, but not read:

    user.password = "secret"
    val pw = user.password // forbidden
    
  2. Another scenario is a property which modifies the internal state, but is not stored itself as a field. (Could be done more elegantly using different design).

    body.thrust_force = velocity
    body.gravity_force = Vector(0, 0, 9.8)
    // only total force accessible, component vectors are lost
    val f = body.forces
    
  3. This pattern is also useful for DSLs of the following kind:

    server {
        port = 80
        host = "www.example.com"
    }
    

    In such cases, values are simply used as one-time settings, and the write-only mechanism described here can prevent accidentally reading a property (which might not be initialized yet).


Limitations

Since this feature was not designed for this use case, it comes with certain limitations:

  • If accessed using a property reference, the compile-time error turns into a runtime error:

    val ref = wo::property
    val x = ref.get() // throws NotImplementedError
    
  • The same is true for reflection.

  • This functionality cannot be outsourced into a delegate, because an error-deprecated getValue() method cannot be used with by.

Bicarb answered 4/3, 2019 at 12:16 Comment(2)
This is interesting and creative, but it feels wrong for being a hack. Fun to know you can do it, however I'd recommend against introducing this trick to production code. Especially as it doesn't really beat a "naive" custom setter alternative in neither clarity nor conciseness.Osmium
I agree with clarity/conciseness at declaration site, as this is not a known approach. At call site however, the opposite could be true -- getX/setX methods are more verbose and may raise the question "why no property?", which is idiomatic in Kotlin. A nice but rather minor side effect: if Kotlin ever introduces write-only properties, the call site syntax would already be correct ;)Bicarb

© 2022 - 2024 — McMap. All rights reserved.