KMM Oddity #2: Initialization order of top-level properties

Yev Kanivets
xorum.io
Published in
4 min readNov 15, 2021

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.

Original commit

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.

Original commit

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.

Original commit

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!

Resources

--

--

Yev Kanivets
xorum.io

Technical Lead Mobile @ 360Learning | KMP enthusiast | Husband & dad | Marathon finisher