I am trying to convert an Android app from Java to Kotlin. There are a few singletons in the app. I used a companion object for the singletons without constructor parameters
if you want to pass a parameter to the singleton in an easier way I think this is better and shorter
object SingletonConfig {
private var retrofit: Retrofit? = null
private const val URL_BASE = "https://jsonplaceholder.typicode.com/"
fun Service(context: Context): Retrofit? {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
and you call it in this easy way
val api = SingletonConfig.Service(this)?.create(Api::class.java)
I saw all the answers. I know this is a repeated answer but if we use the synchronized keyword on the method declaration, it will synchronize the whole method to the object or class. And synchronized block is not deprecated yet.
You can use the following utility class to get the singleton behavior.
open class SingletonWithContextCreator<out T : Any>(val creator: (Context) -> T) {
@Volatile
private var instance: T? = null
fun with(context: Context): T = instance ?: synchronized(this) {
instance ?: creator(context).apply { instance = this }
}
}
You can extend the above-mentioned class whichever class you wanted to make singleton.
In your case the following is the code to make TasksLocalDataSource class singleton.
companion object : SingletonWithContextCreator<TasksDataSource>(::TasksLocalDataSource)
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.
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)
}
class CarsRepository(private val iDummyCarsDataSource: IDummyCarsDataSource) {
companion object {
private var INSTANCE: CarsRepository? = null
fun getInstance(iDummyCarsDataSource: IDummyCarsDataSource): CarsRepository {
if (INSTANCE == null) {
INSTANCE = CarsRepository(
iDummyCarsDataSource = iDummyCarsDataSource)
}
return INSTANCE as CarsRepository
}
}
}
solution with lazy
class LateInitLazy<T>(private var initializer: (() -> T)? = null) {
val lazy = lazy { checkNotNull(initializer) { "lazy not initialized" }() }
fun initOnce(factory: () -> T) {
initializer = factory
lazy.value
initializer = null
}
}
val myProxy = LateInitLazy<String>()
val myValue by myProxy.lazy
println(myValue) // error: java.lang.IllegalStateException: lazy not inited
myProxy.initOnce { "Hello World" }
println(myValue) // OK: output Hello World
myProxy.initOnce { "Never changed" } // no effect
println(myValue) // OK: output Hello World
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