KMM Oddity #2: Initialization order of top-level properties
Kotlin Multiplatform goes Beta this Spring (2022), so it removes all major hassles (Memory Model and Kotlin/Native concurrency, for example) which KMP developers face.
But even with the Stable version (hopefully by the end of 2022), we are going to have a certain number of minor issues or simply oddities while using this relatively young technology (especially with Kotlin/Native). Most of them are already reported in Kotlin’s YouTrack, so go check it before stressing out.
I’ve encountered many such issues myself for the past two years with Kotlin Multiplatform Mobile. In this series of articles, I’ll share my favorite oddities of KMM with explanations and workarounds (if possible).
Top-level properties
In Kotlin, we are able to declare top-level (or global) variables, which are accessible from anywhere in the given module or even the whole application. For example, let’s say that we have a data class Order
:
This class has a name
and an optional dependency
to another Order
. The init
block is here to help us understand when it’s initialized.
While it’s generally a bad idea to declare instances of such class as a global variable, we are going to do it twice in the same file for now. As you can see, orderB
depends on orderA
, so it must be initialized first.
It’s done for demo purposes here, but a real application can require it as well (think about store
, reducers
, and middleware
from Redux architectural pattern, which are all global variables by default).
When run, we see the expected order of initialization in logs of both, Android and iOS applications: Init orderA
/ Init orderB
.
Single-file initialization order
When we have all connected top-level variables in the same file, things are simple. To check it, swap the orderA
with orderB
.
Do you see? Android Studio will complain about the fact that orderA must be initialized
, that means it should be located on the top of orderB
.
Separate files initialization order
But what happens when we extract orderA
to OrderA.kt
and orderB
to OrderB.kt
? Compilation errors go away, and we can run both apps without any problems with the same expected logs.
You can try it yourself, using this commit.
But now, for some reason, we need to inverse dependency, so orderA
depends on orderB
, while they are in different files.
As an Android developer, I’m used to the fact that such things are simply working. Java/Kotlin runtimes resolve such dependencies automagically (lazily to be exact), so in logs, we see the expected sequence of Init orderB
/ Init orderA
— thanks Kotlin/JVM for that :)
But from the iOS side, we surprisingly see the inversed sequence of Init orderA
/ Init orderB
, which is bad, because orderA
now depends on orderB
. So how it could be initialized without it?
Let’s what is inside the orderA.depedency?.name
to make things clear by updating logs in init
to Init order$name with dependency to $dependency
.
On Android it gives us the expected result:
Init orderB with dependency to null
Init orderA with dependency to Order(name=B, dependency=null)
But on iOS it messes up the whole thing with the following output:
Init orderA with dependency to null
Init orderB with dependency to null
Kotlin/Native global properties initialization order
I wasn’t able to find an exact piece of documentation on how it works, but experimentally, it seems that we can assume that top-level properties are initialized in alphabetical order of the files (and packages) they are located in.
For example, if we swap files for orderA
and orderB
as in the following commit, the initialization sequence is correct in both apps.
Conclusions
In the first place, it’s better to not have top-level properties at all not only in your Kotlin Multiplatform code but in any code ;) If you need them anyway (as with Redux in my case), it’s recommended to place them in packages/files so they are initialized from top to bottom in your expanded source tree.
Also, it looks like the issue should be fixed in one of the following releases with a new garbage collector: https://youtrack.jetbrains.com/issue/KT-46771. Meanwhile, do the KMP and have fun!