dbaddorf
dbaddorf

Reputation: 35

How to have a custom setter with a named argument and default

I have a Kotlin class with named arguments and defaults for non-specified arguments. I am trying to figure out how to create a custom setter for one argument and just can't seem to figure out what I'm doing wrong - although it's probably simple. Here is a simplified version of the class:

class ItemObject(var itemNumber: String = "",
             var itemQty: Int = 0)

I can use the properties of this class without issues itemObject.itemQty = itemObject.itemQty + 1 (accessing both the getter and setter).

However, I'd like to make a custom setter to prevent the itemQty from going below zero. So I've tried many variations on the following theme:

class ItemObject(var itemNumber: String = "",
             itemQty: Int = 0) {

var itemQty: Int = 0
    set(value: Int) =
        if (value >= 0) {
            itemQty = value // Don't want to go negative
        } else {
        }

}

This compiles without issue, but seems to keep defaulting itemQty to zero (or something like this - I haven't tracked it down).

Can anyone point me in the correct direction? I'd certainly appreciate it - this is my first Kotlin project and could use a bit of help. :)

Upvotes: 0

Views: 755

Answers (3)

Francesc
Francesc

Reputation: 29260

This is what vetoable is for:

var quantity: Int by Delegates.vetoable(0) { _, _, new ->
    new >= 0
}

Return true to accept the value, return false to reject it.

Upvotes: 3

Sergei Rybalkin
Sergei Rybalkin

Reputation: 3453

There are different ways of preventing value going below zero. One is unsigned types. Experimental at the moment. UInt in your case

class ItemObject(var itemNumber: String = "",
                 var itemQty: UInt = 0u
)

If you don't care about value overflow - it might be an option

Another way is Property Delegation

class ItemObject(var itemNumber: String = "", itemQty: Int = 0) {
    var itemQty: Int by PositiveDelegate(itemQty)
}

class PositiveDelegate(private var prop: Int) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = prop

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value >= 0) prop = value
    }
}

Third one with custom setters is described in other answers.

Upvotes: 0

Jonas Wilms
Jonas Wilms

Reputation: 138247

Well, you initualize the real field to 0 and ignore the passed value ... Instead, initialize the property with the passed constructor parameter:

  class Item(_itemQty: Int = 0) {
     var itemQty: Int = Math.max(0, _itemQty)
        set(v) { field = Math.max(0, v) }
  }

(I used two different Identifiers to seperate the parameter and the property, as mentioned in the comments you can also use the same name for the property and the parameter [but be careful, that could add confusion]).

You should also set the backing field, not the setter itself which will end in endless recursion.

If the setter is rather complicated and also needs to be applied to the initial value, then you could initialize the property, and execute the setter afterwards with the parameter:

  class Item(_itemQty: Int = 0) {
     var itemQty: Int = 0
        set(v) { field = Math.max(0, v) }

     init { itemQty = _itemQty }
  }

As an opinion-based side note, item.itemQty is not really descriptive, item.quantity would be way more readable.

Upvotes: 1

Related Questions