Kotlin singletons with argument

object has its limits

In Kotlin, the singleton pattern is used as a replacement for static members and fields that don’t exist in that programming language. A singleton is created by simply declaring an object.

object SomeSingleton

Contrary to a class, an object can’t have any constructor, but init blocks are allowed if some initialization code is needed.

object SomeSingleton {
init {
println("init complete")
}
}

The object will be instantiated and its init blocks will be executed lazily upon first access, in a thread-safe way. To achieve this, a Kotlin object actually relies on a Java static initialization block. The above Kotlin object will be compiled to the following equivalent Java code:

public final class SomeSingleton {
public static final SomeSingleton INSTANCE;

private SomeSingleton() {
INSTANCE = (SomeSingleton)this;
System.out.println("init complete");
}

static {
new SomeSingleton();
}
}

This is the preferred way to implement singletons on a JVM because it enables thread-safe lazy initialization without having to rely on a locking algorithm like the complex double-checked locking. By simply using an object declaration in Kotlin, you are guaranteed to get a safe and efficient singleton implementation.

Singletons: forever alone

Passing an argument

But what if the initialization code requires some extra argument? Because a Kotlin object can’t have any constructor, you can’t pass any argument to it.

There are valid cases for which passing an argument to a singleton initialization block is the recommended pattern. The alternative requires the singleton class to be aware of some external component to be able to retrieve that argument, violating the principle of separation of concerns and making the code less reusable. To mitigate the issue, that external component could be a dependency injection system. It’s a valid solution but you don’t always want to use that kind of library and there are some cases where it can’t be used, like we’ll see in the following Android example.

Another scenario where you have to rely on a different way to manage a singleton in Kotlin is when the concrete implementation of the singleton is generated by an external tool or library (Retrofit, Room, …) and its instance is retrieved using a custom builder or factory method. In that case, you usually declare the singleton as an interface or abstract class and it can’t be an object.

An Android use case

On the Android platform, you often need to pass a Context instance to initialization blocks of singleton components so they can retrieve file paths, read settings or access services, but you also want to avoid keeping a static reference to it (even if a static reference to the application Context is technically safe). There are two ways to achieve that:

  • Early initialization: All components are initialized by calling their public init functions at startup time in Application.onCreate() where the application Context is available, before (almost) any other code runs. This simple solution has the main downside of slowing down the application startup by blocking the main thread, initializing all components including those that are not going to be used immediately. Another less known issue is that content providers may be created before this method is called (as stated in the documentation), so if a content provider makes use of a global application component, that component must be able to be initialized before Application.onCreate() or your application may crash.
  • Lazy initialization: This is the recommended way. The component is a singleton and a function returning its instance takes a Context argument. The singleton instance will be created and initialized using this argument the first time it’s invoked. This requires some synchronization mechanism in order to be thread-safe.
    An example of standard Android component using this pattern is LocalBroadcastManager:
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)

A reusable Kotlin implementation

We can encapsulate the logic to lazily create and initialize a singleton with argument inside a SingletonHolder class. In order to make that logic thread-safe, we need to implement a synchronized algorithm and the most efficient one — which is also the hardest to get right — is the double-checked locking algorithm.

open class SingletonHolder<out T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null

fun
getInstance(arg: A): T {
val i = instance
if
(i != null) {
return i
}

return synchronized(this) {
val
i2 = instance
if
(i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}

Note that adding the @Volatile annotation to the instance field is required for the algorithm to work properly.

This may not be the most compact or elegant Kotlin code, but it’s the one producing the most efficient bytecode for the double-checked locking algorithm. Trust the Kotlin authors on this: this code is actually borrowed directly from the implementation of the lazy() function in the Kotlin standard library, which is synchronized by default. It has been modified to allow passing an argument to the creator function.

Given its relative complexity, it’s not the kind of code you want to write (or read) more than once, so the goal is to reuse that SingletonHolder class everytime you have to implement a singleton with argument.

The logical place to declare the getInstance() function is inside the companion object of the singleton class, which allows it to be called by using simply the singleton class name as qualifier, similarly to a static method in Java. One powerful feature that Kotlin companion objects offer is their ability to inherit from a base class like any other object, which enables something comparable to static-only inheritance. In this case, we want to use SingletonHolder as a base class for the companion object of the singleton, in order to reuse and automatically expose its getInstance() function on the singleton class.

As for the creator function passed as argument to the SingletonHolder constructor, a custom lambda could be declared inline but the common solution is to pass a reference to the private constructor of the singleton class. The final code for a singleton with argument would look like this:

class Manager private constructor(context: Context) {
init {
// Init using context argument
}

companion object : SingletonHolder<Manager, Context>(::Manager)
}

Not as short as the object notation, but probably the next best thing.

The singleton may now be invoked using the following syntax and its initialization will be lazy and thread-safe:

Manager.getInstance(context).doStuff()

You can also use this idiom when a singleton implementation is generated by an external library and the builder requires an argument. Here’s an example using the Room Persistence Library for Android:

@Database(entities = arrayOf(User::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {

abstract fun userDao(): UserDao

companion object : SingletonHolder<UsersDatabase, Context>({
Room.databaseBuilder(it.applicationContext,
UsersDatabase::class.java, "Sample.db")
.build()
})
}

Note: When the builder doesn’t require an argument, you can simply use a lazy delegated property instead:

interface GitHubService {

companion object {
val instance: GitHubService by lazy {
val
retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build()
retrofit.create(GitHubService::class.java)
}
}
}

I hope you found these code snippets useful. If you want to suggest other ways to solve this problem or have questions, don’t hesitate to start a discussion in the comments section. Thank you for reading!