Reputation: 7979
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:
Additional notes:
TREE_9
, and they all update, so the definition included is definitely the one being used.Upvotes: 2
Views: 2447
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