Jake Lee
Jake Lee

Reputation: 7979

Kotlin enum val is returning null, despite being set at compile time

I have a Kotlin enum with multiple fields. One of these fields (enterableBoard) is the value of another enum. At runtime, this val is null despite being set.

I've never seen something like this before, and other fields are working fine, even the one referencing the current Item! I've included the full enum definitions, as I'm not sure what is relevant.

Item enum and a value, where enterableBoard is unexpectedly null instead of Board.SPACEX_EARLY_TREES.

enum class Item(
    val chain: ItemChain,
    val tier: Int,
    @StringRes val title: Int,
    @DrawableRes val image: Int,
    val generatorMaxEnergy: Int = 0, 
    val enterableBoard: Board? = null,
    val mergeBonus: Pair<Item, Int>? = null, 
    val redeemable: Pair<InfoLong, Int>? = null
) {
    TREE_9(ItemChain.TREE, 9, R.string.item_tree9, R.drawable.item_tree9,
        generatorMaxEnergy = 50, enterableBoard = Board.SPACEX_EARLY_TREES)
}

Board enum and the relevant value:

enum class Board(
    val campaign: Campaign,
    @StringRes val title: Int,
    @StringRes val description: Int,
    @DrawableRes val background: Int,
    @DrawableRes val image: Int,
    val unlockCost: Int,
    val template: List<Item>
) {
    SPACEX_EARLY_TREES(Campaign.SPACEX_EARLY, R.string.board_spacex_early_tree_title, R.string.board_spacex_early_tree_description,
        R.drawable.background5, R.drawable.background5, 0,
        listOf(Item.FRUITVEG_1, Item.FRUITVEG_2, Item.FRUITVEG_1))
}

Here's a screenshot of enterableBoard being unexpectedly null:

android studio evaluate screen

Additional notes:

Upvotes: 2

Views: 2447

Answers (1)

Tenfour04
Tenfour04

Reputation: 93581

I'm pretty sure this is because you have a reciprocal reference. Board references Item in its constructor and vice versa. One of the two enum classes' members are instantiated before the other, so the references to the others values are still null at the time it's referencing them. This is one of the sneaky ways you can get a non-nullable reference to a null in Kotlin (it still would happen if your Board parameter were non-nullable) by doing something you're not supposed to, like calling an open function from a constructor.

Basically, enums cannot safely reference each other reciprocally because of how they're compiled and behave at instantiation time. Should the compiler show a warning or error for this condition? Probably, but it apparently doesn't.

Here's a minimal reproducable example of the problem:

enum class Item (val board: Board) {
    X(Board.Y)
}

enum class Board(val item: Item) {
    Y(Item.X)
}

fun main() {
    Item.X
    println(Board.Y.item)
}

prints null because the reference to Item.X causes Item's members to be try to be instantiated first, but since its constructor references Board, then Board is actually instantiated first and uses the still null Item.X reference.

Upvotes: 6

Related Questions