Private getter and public setter for a Kotlin property

JavaKotlinKotlin Interop

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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"
}

Solution 3 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRandy Sugianto 'Yuku'View Question on Stackoverflow
Solution 1 - JavaAlexander UdalovView Answer on Stackoverflow
Solution 2 - JavamiensolView Answer on Stackoverflow
Solution 3 - JavaTheOperatorView Answer on Stackoverflow