Search code examples
kotlinoopinheritanceoverridingabstract-class

Kotlin: how to have abstract values as default arguments for abstract functions?


I have a sealed class that represents my screen states, and the different screen states are data classes, since I want to be able to copy them while changing just one value.

Unfortunately Kotlin doesn't support sealed data classes, so I am trying as close as possible to simulate this behavior:

sealed class ScreenState {
    abstract val item: DTOPurchasableItem
    abstract val balance: String?
    abstract fun copyScreenState(item: DTOPurchasableItem = item, balance: String? = balance): ScreenState

    data class NotEnoughMoney(override val item: DTOPurchasableItem, override val balance: String?) : ScreenState() {
        override fun copyScreenState(item: DTOPurchasableItem, balance: String?): ScreenState = copy(item = item, balance = balance)

    }

    data class ItemAlreadyOwned(override val item: DTOPurchasableItem, override val balance: String?) : ScreenState() {
        override fun copyScreenState(item: DTOPurchasableItem, balance: String?): ScreenState = copy(item = item, balance = balance)

    }
}

But this unfortunately doesn't work because the IDE accuses "item" and "balance" of not being initialized:

That doesn't make a lot of sense because whenever the abstract function would be invoked, the "item" and "balance" attributes would have been initialized already...

I want to be able to:

val screenState = ScreenState.NotEnoughMoney(item = generateItem(), "2000")
val newState = screenState.copyScreenState(balance = "100") // it would therefore "reuse" the item that already existed, implicitly, without me need to pass it as argument.

Any way to achieve this? Important to note that some screen states have more arguments that belong just to those states.


Solution

  • This is happening because your parameters and properties have the same names. The item you're trying to use to initialize the parameter is the parameter itself, not the property. Prepend with this. to specify you want the property, since it is in an outer scope.

    abstract fun copyScreenState(item: DTOPurchasableItem = this.item, balance: String? = this.balance): ScreenState
    

    As for why it interprets it this way, allowing parameters to reference parameters before the function is invoked, it's because default parameter expressions can reference preceding parameters:

    abstract fun foo(bar: String, baz: Int = bar.length)
    

    So when you use the name of a parameter, it resolves to the parameter first before any property of the class with matching name. And your error is because you were accessing that parameter in its own expression.