:Publish Date: 2025-03-09 .. role:: kotlin(code) :language: kotlin Kotlin Singleton Patterns ========================= My brother was looking for an Android app to perform a single task: A big button on top of other apps to store the current location when pressed. We could not find one which fits that bill and I set out to develop one myself. As many projects do, the app turned from a "let's do it this weekend" into a bigger project, mostly because I got to learn Kotlin and the Android ecosystem. What does an Android app have to do with singletons? Databases. There are many different ways to `persist data`_ in Android applications. The current (2025) recommended way to store structured data is to use Room_, a database library backed by SQLite_. This is the perfect fit to store our location data. I mostly followed the `Room tutorial`_ to set everything up: 1. Add some dependencies (let's skip this) 2. Write an entity-backed |dao| 3. Write a database class to access the |dao| 4. Instantiate the database The second and third steps can be boiled down to this example, in which we store "Spots": .. code:: kotlin @Entity data class Spot( @PrimaryKey val uid: Int, val example: String, ) @Dao interface SpotDao { @Query("SELECT * FROM spot") fun getAll(): List @Insert fun insertAll(vararg spots: Spot) } @Database(entities = [Spot::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun spotDao(): SpotDao } This is of course tuned down: We can only insert and retrieve records; to delete the records, we need to delete the app storage. But these steps don't show any singletons yet... The final step, instantiating the AppDatabase, is where it becomes interesting. The tutorial uses this pattern: .. code:: kotlin val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build() So they use a builder, pass some arguments to it, and build the instance. Curiously, the tutorial also points out the following: If your app runs in a single process, you should follow the singleton design pattern when instantiating an AppDatabase object. Each :external:android:class:`RoomDatabase` instance is fairly expensive[.] There is more information about how to deal with multiple processes, which should not be relevant for our app. I understand it that we have a single process with multiple threads: At least the main thread for the |GUI| and an |IO| thread to which we can dispatch :external:kt:val:`IO` coroutines. So we should implement the singleton pattern! I know roughly how to do singletons in various programming languages, but each language has its own conventions and tools for it. However, it mostly boils down to this pseudo code: .. code:: text class SomeSingletonClass: private static instance = null public static function getInstance() if instance == null instance = SomeSingletonClass() return instance This means that we usually have a class which holds an instance of itself. In many languages, this is denoted as *static* or a *class member* to keep it globally available since classes are usually global. If possible, the instance is also marked private so that it cannot be changed accidentally. Additionally, there is an accessor which instantiates said instance when required and returns it. Many implementations aim to be thread-safe by using some sort of locking, too, which is something we also require as our app uses multiple threads. So I looked up how to do it and in Kotlin there are at least two very neat ways to create singletons. The first way is to declare an object, which is by definition only available once, similar to a class declaration: .. code:: kotlin object Singleton { fun doSomething() { } } The second is to use :external:kt:fun:`lazy`, a built-in method which allows lazy initialization on the fly: .. code:: kotlin val db: AppDatabase by lazy { Room.databaseBuilder( applicationContext, AppDatabase::class.java, "app_database" ).build() } Both instantiation variants are thread-safe (for :external:kt:fun:`lazy` see also `Lazy Initialization in Kotlin`_ on Baeldung). For many applications, using the :external:kt:kw:`object` or :external:kt:fun:`lazy` is the way to go. There is a problem for the Room database though, you may have spotted it already: It requires an argument, specifically, the `application context`_. This application context somehow needs to be passed to the initialization function. The :external:kt:kw:`object` method will not work in this case, as the object is initialized earlier than we have a chance to grab the application context and pass it. For :external:kt:fun:`lazy`, we might have chance: .. code:: kotlin @Database(entities = [Spot::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun spotDao(): SpotDao val db: AppDatabase by lazy { // Room build() } } No, this is too early, and we cannot pass a parameter here, either. Additionally, the ``db`` object is not *static*, that means each instance of AppDatabase would hold its own lazy-initialized copy. As it turns out, there is no *static* in Kotlin; instead, we must use a :external:kt:kw:`companion object`, which creates class-level functions and properties [#companion]_. .. code:: kotlin @Database(entities = [Spot::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun spotDao(): SpotDao companion object Companion { val instance: AppDatabase by lazy { // Room build() } } } Again, we cannot pass arguments. Let's create the classic ``getInstance`` method and pass the argument. .. code:: kotlin @Database(entities = [Spot::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun spotDao(): SpotDao companion object Companion { // We hold the private instance here and initialize it to null private var instance: AppDatabase? = null fun getDatabase(applicationContext: Context): AppDatabase { if (instance == null) { instance = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "app_database" ).build() } return instance } } } This looks promising! It is not thread-safe yet, but we are able to pass an application context to the instantiation. I also checked the example repository for the inventory app, which contains very similar code in `InventoryDatabase.kt#L32-50`_: .. code:: kotlin companion object Companion { // I added the Companion name here for Pygments @Volatile private var Instance: InventoryDatabase? = null fun getDatabase(context: Context): InventoryDatabase { // if the Instance is not null, return it, otherwise create a new database instance. return Instance ?: synchronized(this) { Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database") /** * Setting this option in your app's database builder means that Room * permanently deletes all data from the tables in your database when it * attempts to perform a migration with no defined migration path. */ .fallbackToDestructiveMigration() .build() .also { Instance = it } } } } It uses :external:kt:anno:`@Volatile` to make the instance field atomic and :external:kt:fun:`synchronized` as a locking mechanism. I particularly like the :kotlin:`.also { Instance = it }` bit (:external:kt:fun:`also`), it is a very nice pattern to return the instance and assigning it to the stored instance. The only thing I want to change is to use full :wiki:`Double-checked locking`. As it is written above, it can happen that two threads determine the instance is :external:kt:kw:`null` and attempt to acquire the lock. Once a thread acquires the lock, it will create an instance and assign it, effectively allowing the second thread to overwrite the first. To prevent this, a second :external:kt:kw:`null` check can be done inside the synchronized block, leading to the following final result: .. code:: kotlin companion object Companion { @Volatile private var instance: AppDatabase? = null fun getDatabase(applicationContext: Context): AppDatabase { return instance ?: synchronized(this) { return instance ?: Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "app_database" ).build().also { instance = it } } } } .. rubric:: Footnotes .. [#companion] The name of the companion object is optional and defaults to *Companion*, but the Pygments_ lexer cannot parse it. See :gh:`pygments/pygments#2525`. .. _`persist data`: https://developer.android.com/training/data-storage/ .. _Room: https://developer.android.com/training/data-storage/room .. _`Room tutorial`: Room_ .. _SQLite: https://www.sqlite.org/index.html .. _Pygments: https://pygments.org .. _`Lazy Initialization in Kotlin`: https://www.baeldung.com/kotlin/lazy-initialization#lazy-initialization-in-kotlin .. _`application context`: https://developer.android.com/reference/android/content/Context#getApplicationContext() .. _`pygments/pygments#2525`: https://github.com/pygments/pygments/issues/2525 .. _`InventoryDatabase.kt#L32-50`: https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app/blob/e0773b718f2670e401c039ee965879c5e88ca424/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt .. |dao| replace:: :abbr:`DAO (Data Access Object)` .. |IO| replace:: :abbr:`IO (Input/Output)` .. |GUI| replace:: :abbr:`GUI (Graphical User Interface)` .. spelling:word-list:: Baeldung Pygments