Knowing this Kotlin pitfall can save you from bugs

Bartłomiej Klocek
Nerd For Tech
Published in
3 min readSep 12, 2021

Kotlin is an awesome language, but its “syntax sugar” can sometimes be confusing. Although this particular thing is mentioned in the documentation, it’s easy to miss. Let’s start with an example:

We have a class AspectRatioScreen with one property, which defaults to old good 4:3. Then we have ResolutionScreen with width and height properties. Their setters automatically update the aspect ratio.

In main we create a ResolutionScreen object and print its parameters. By default, the resolution is set to 1920x1080 which corresponds to 16:9 aspect ratio. But the program output is:

Screen resolution: 1920x1080, ratio: 4/3

The aspect ratio doesn’t match the resolution. Why? The Kotlin documentation says:

If you define a custom setter, it will be called every time you assign a value to the property, except its initialization.

This means that setting width’s and height`s initial dimensions doesn’t call their setters. One way to fix it would be to add a init block inside the ResolutionScreen class:

class ResolutionScreen : AspectRatioScreen() {
var width = 1920
// ...
var height = 1080
// ...

init {
width = 1920
height = 1080
}
}

This will re-assign the default values and call setters. The displayed result is now correct:

Set width to 1920
Set height to 1080
Screen resolution: 1920x1080, ratio: 16/9

This solution has one drawback: You have to declare the default value twice because properties cannot be left without an initializer. A workaround would be to define constants: private const val DEFAULT_WIDTH = 1920 and using them in both places.

The simplest solution is to call reacalculateAspectRatio() directly inside the init block:

class ResolutionScreen : AspectRatioScreen() {
var width = 1920
// ...
var height = 1080
// ...

init {
recalculateAspectRatio()
}
}

This is the clearest solution for this particular situation. There’s another way of doing this, which looks better when there is a single property, for which we want to initialize its backing property:

class Duration {
var durationInSeconds: Int = 1
}
class EnhancedDuration : Duration() {
var durationInHours = 1.also { durationInSeconds = 3600 * it }
// ...
}

This syntax has one advantage — we initialize the backing property (durationInSeconds in this case) in the same place we initialize the original property.

In the case of our example it would look like this:

class ResolutionScreen : AspectRatioScreen() {
var width = 1920
set(value) {
field = value
recalculateAspectRatio()
}
var height = 1080.also { aspectRatio = width divBy it }
set
(value) {
field = value
recalculateAspectRatio()
}

// ...
}

The also block is only present in the height initializer, because the initialization goes from top to bottom — we cannot use width nor height before they’re initialized. Even inside the also block, height cannot be used, because it’s yet to be initialized. That’s why in cases where two or more properties are somehow related, it’s better to use init blocks.

--

--

Bartłomiej Klocek
Nerd For Tech

Enthusiast of electronics and all kinds of software development — from web apps to embedded systems. Expo open-source contributor at Software Mansion.