App Failures By Koin: Story 1 — Be Careful With Singletons
This story will show you the importance of choosing the right provider functions in your Koin module declarations.

Nothing new to most of you, like a rule of thumb: “think twice before you decide smth is a singleton”. And I’m pretty sure you’re already doing this like we were doing. Hence now you will most probably go to skip this part :).. but wait a min, check the code first.
The Roadmap
As a true lover of Koin DI framework, in this series of stories, I’d like to share some of the true app failures my team gained when we started using Koin. (Most of them were found in production, unfortunately…)
Story 1 — Be Careful With Singletons (we are here)
Story 2 — Unreliable Dynamic Loading
Story 3 — Issue With sharedViewModel()
Story 4 — Does It Worth Sharing a ViewModel?
… to be continued…
Koin DSL
Let’s remind ourselves what is Koin first:
A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin, using functional resolution only: no proxy, no code generation, no reflection.
Koin is a DSL, a light container and a pragmatic API
DSL (domain-specific language) makes Koin declarations concise and handy so that with a couple of lines you can define your module. Usually your module declaration is smth like (example from the official doc):
// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)
val moduleA = module {
// Singleton ComponentA
single { ComponentA() }
}
val moduleB = module {
// Singleton ComponentB with linked instance ComponentA
single { ComponentB(get()) }
}For those of you who are not yet familiar with Koin injection and DSL functions, please reference to the official documentation. Now, when we all are on the same page, let’s move to the real app example and failure explanation.
What Went Wrong?
So imagine you have some singleton, some service in your app, let’s call it SharedDataContainer. This class holds a bus of periodically updated information, let it be a LiveData
interface SharedDataContainer {
val appData: LiveData<AppConfiguration> fun appData() =
appData.value ?: error("SharedDataContainer is not initalised")
}class SharedDataContainerImpl : SharedDataContainer { ... }single<SharedDataContainer> { SharedDataContainerImpl() }All fine so far, right? Application was growing, AppData is grouping more and more valuable information, received from server, some dynamic app parameters, theme, etc:
data class AppData(
val userSettings: UserSettings,
val defaultLocation: Location,
val theme: Theme,
...
)data class Theme(
val attributes: Attributes,
val styles: Styles
)data class Attributes(
val colors: Colors,
val fonts: Fonts,...
)A question now — what would you do to use “colors” in one of your views? Okay, you may inject your SharedDataContainer and access it via properties:
val sharedDataContainer: SharedDataContainer by inject()
...
val color = sharedDataContainer.appData().theme.attributes.colors.accentColorBut you’re a bit lazy to do this every time you need to use colors values, or fonts, and after some time you think — wait, I can optimise this, I can extend DI module declaration to write less words in injection:
single<SharedDataContainer> { SharedDataContainerImpl() }
single<AppData> { get<SharedDataContainer>().appData() }
single<Theme> { get<AppData>().theme }
single<Attributes> { get<Theme>().attributes }
single<Colors> { get<Attributes>().colors }And now life is perfect :) Yeap, injection works like a charm, great job ;) Just compare, isn’t it better?
val colors: Colors by inject()
...
val color = colors.accentColorCongratulations 🥳 , just now we not only improved the code & saved our fingers from typing more words, but we also fall down…🥺

Understanding & Fixing
Have you already caught the problem? Might be yes, because the story has a tip, and you already know where to pay attention to 👀.. But if you have tens of modules declarations, thousands of lines of code, and once someone tells you “part of your views don’t receive server updates for the UI” — how much time and effort would you spend to recognize the problem? How much time of debugging it will take to catch the diverged theme attributes in short and long injection ways? I believe it won’t be that fast as reading this post 😏
So what’s just happened ? You just saw the wrong way of declaring non static, changeable data via ‘shortcuts’.

Let’s dispel doubts and check the right version of being concise in injection and at the same time guarantee the correct work of the app.
single<SharedDataContainer> { SharedDataContainerImpl() }
factory<AppData> { get<SharedDataContainer>().appData() }
factory<Theme> { get<AppData>().theme }
factory<Attributes> { get<Theme>().attributes }
factory<Colors> { get<Attributes>().colors }One word can change the situation dramatically, like “factory” in this case. Ultimately, we have the expected app behavior and consistency in all injection alternatives. Declaration via “factory” guarantees that the latest data is taken each time it’s referenced in injection or assigned to a variable, whereas “single” makes a copy of data inside the koin component when it’s first referenced. As a result, from the UI perspective application behavior was so weird & inconsistent, that it took some hours to understand what’s the root of evil…
To sum up the story, Koin DSL module declarations by examples usually look very obvious when you read documentation and guidelines. However, when you are writing your code it’s quite easy to make a mistake just because you automatically chose the wrong providing function. Hope this article will save you some hours of debugging and you’ll be careful with singletons ;) Thanks for reading, and let’s see in the next stories.
References
- Koin documentation https://doc.insert-koin.io/#/introduction
- Koin DSL documentation https://doc.insert-koin.io/#/koin-core/dsl
- Koin on GitHub https://github.com/InsertKoinIO/koin





