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()