Kotlin singletons with argument

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
object SomeSingleton {
init {
println("init complete")
}
}
public final class SomeSingleton {
public static final SomeSingleton INSTANCE;

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

static {
new SomeSingleton();
}
}
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.

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:

  • 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: Any, 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
}
}
}
}
class Manager private constructor(context: Context) {
init {
// Init using context argument
}

companion object : SingletonHolder<Manager, Context>(::Manager)
}
Manager.getInstance(context).doStuff()
@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()
})
}
interface GitHubService {

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

Android developer from Belgium, blogging about advanced programming topics.