Singleton with parameter in Kotlin

Here's a neat alternative from Google's architecture components sample code, which uses the also function:

class UsersDatabase : RoomDatabase() {

    companion object {

        @Volatile private var INSTANCE: UsersDatabase? = null

        fun getInstance(context: Context): UsersDatabase =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
            }

        private fun buildDatabase(context: Context) =
            Room.databaseBuilder(context.applicationContext,
                    UsersDatabase::class.java, "Sample.db")
                    .build()
    }
}

I am not entirely sure why would you need such code, but here is my best shot at it:

class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
    private val mDbHelper = TasksDbHelper(context)

    companion object {
        private var instance : TasksLocalDataSource? = null

        fun  getInstance(context: Context): TasksLocalDataSource {
            if (instance == null)  // NOT thread safe!
                instance = TasksLocalDataSource(context)

            return instance!!
        }
    }
}

This is similar to what you wrote, and has the same API.

A few notes:

  • Do not use lateinit here. It has a different purpose, and a nullable variable is ideal here.

  • What does checkNotNull(context) do? context is never null here, this is guarantied by Kotlin. All checks and asserts are already implemented by the compiler.

UPDATE:

If all you need is a lazily initialised instance of class TasksLocalDataSource, then just use a bunch of lazy properties (inside an object or on the package level):

val context = ....

val dataSource by lazy {
    TasksLocalDataSource(context)
}

You can declare a Kotlin object, overloading "invoke" operator.

object TasksLocalDataSource: TasksDataSource {
    private lateinit var mDbHelper: TasksDbHelper

    operator fun invoke(context: Context): TasksLocalDataSource {
        this.mDbHelper = TasksDbHelper(context)
        return this
    }
}

Anyway I think that you should inject TasksDbHelper to TasksLocalDataSource instead of inject Context


Thread-Safe Solution # Write Once; Use Many;

It's a good solution to create a class implementing the logic of singleton which also holds the singleton instance, like the following.

It instantiates the instance using Double-Check Locking in a synchronized block to eliminate possibility of race condition in multi-threaded environments.

SingletonHolder.kt

open class SingletonHolder<out T, in A>(private val constructor: (A) -> T) {

    @Volatile
    private var instance: T? = null

    fun getInstance(arg: A): T =
        instance ?: synchronized(this) {
            instance ?: constructor(arg).also { instance = it }
        }
}

Usage

Now in each class that you want to be singleton, write a companion object extending the above class. SingletonHolder is a generic class that accepts type of target class and its requiring parameter as generic params. It also needs a reference to the constructor of target class which is used for instantiating an instance:

class MyManager private constructor(context: Context) {

    fun doSomething() {
        ...
    }

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

Finally:

MyManager.getInstance(context).doSomething()

Tags:

Android

Kotlin