Singleton with parameter in Kotlin

AndroidKotlin

Android Problem Overview


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. There is another singleton that takes a constructor parameter.

Java code:

public class TasksLocalDataSource implements TasksDataSource {

    private static TasksLocalDataSource INSTANCE;

    private TasksDbHelper mDbHelper;

    // Prevent direct instantiation.
    private TasksLocalDataSource(@NonNull Context context) {
        checkNotNull(context);
        mDbHelper = new TasksDbHelper(context);
    }

    public static TasksLocalDataSource getInstance(@NonNull Context context) {
        if (INSTANCE == null) {
            INSTANCE = new TasksLocalDataSource(context);
        }
        return INSTANCE;
    }
}

My solution in kotlin:

class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {

    private val mDbHelper: TasksDbHelper

    init {
        checkNotNull(context)
        mDbHelper = TasksDbHelper(context)
    }

    companion object {
        lateinit var INSTANCE: TasksLocalDataSource
        private val initialized = AtomicBoolean()

        fun getInstance(context: Context) : TasksLocalDataSource {
            if(initialized.getAndSet(true)) {
                INSTANCE = TasksLocalDataSource(context)
            }
            return INSTANCE
        }
    }
}

Am I missing anything? Thread safety? Laziness ?

There were a few similar questions but I don't like the answers :)

Android Solutions


Solution 1 - Android

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

Solution 2 - Android

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

Solution 3 - Android

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

Solution 4 - Android

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

Solution 5 - Android

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)

Solution 6 - Android

The method synchronized() is marked as deprecated in the common standard library so an alternative would be this:

class MySingleton private constructor(private val param: String) {

    companion object {
        @Volatile
        private var INSTANCE: MySingleton? = null

        @Synchronized
        fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
    }
}

Solution 7 - Android

If the only parameter you need is the application Context, then you can initialize it to a top level val, early in a ContentProvider, like the Firebase SDK does.

Since declaring a ContentProvider is a bit cumbersome, I made a library that provides a top level property named appCtx for all places where you don't need an Activity or other special lifecycle bound context.

Solution 8 - Android

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 initialized

myProxy.initOnce { "Hello World" }
println(myValue) // OK: output Hello World

myProxy.initOnce { "Never changed" } // no effect
println(myValue) // OK: output Hello World

Solution 9 - Android

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 10 - Android

If you looking for a base SingletonHolder class with more than one argument. I had created the SingletonHolder generic class, which supports to create only one instance of the singleton class with one argument, two arguments, and three arguments.

link Github of the base class here

Non-argument (default of Kotlin):

object AppRepository 

One argument (from an example code in the above link):

class AppRepository private constructor(private val db: Database) {
    companion object : SingleArgSingletonHolder<AppRepository, Database>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db)

Two arguments:

class AppRepository private constructor(private val db: Database, private val apiService: ApiService) {
    companion object : PairArgsSingletonHolder<AppRepository, Database, ApiService>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db, apiService)

Three arguments:

class AppRepository private constructor(
   private val db: Database,
   private val apiService: ApiService,
   private val storage : Storage
) {
   companion object : TripleArgsSingletonHolder<AppRepository, Database, ApiService, Storage>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db, apiService, storage)

More than 3 arguments:

To implement this case, I suggest creating a config object to pass to the singleton constructor.

Solution 11 - Android

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)

Solution 12 - Android

This is an example of a singleton in kotlin I test it with threads and there was no exception

class ShoppingCartClassic private  constructor() {

   private var outfits: ArrayList<Outfit> = ArrayList()
   
   companion object{
       @Volatile
       private var instance: ShoppingCartClassic? = null

       fun get(): ShoppingCartClassic {
           synchronized(this) {
//                return instance?: ShoppingCartClassic()  // I commented this because I got lower performance 
               if (instance == null) {
                   instance = ShoppingCartClassic()
               }
               return this.instance!!
           }
       }
   }

   fun addOutFit(outfit: Outfit){
       outfits.add(outfit)
   }

   fun removeOutFit(outfit: Outfit){
       outfits.remove(outfit)
   }

   fun checkout() :List<Outfit>{

       return outfits
   }
}

Here is the test

companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val outfit1 = Outfit(
                pants = Pants("short pants1", Color.BLACK),
                shoes = Shoes("cool shoes1", Color.BLACK),
                shirt = Shirt("my shirt1", Color.GREEN)
            )
            val outfit2 = Outfit(
                pants = Pants("short pants2", Color.BLACK),
                shoes = Shoes("cool shoes2", Color.BLACK),
                shirt = Shirt("my shirt2", Color.BLUE)
            )
            val outfit3 = Outfit(
                pants = Pants("short pants3", Color.BLACK),
                shoes = Shoes("cool shoes3", Color.BLACK),
                shirt = Shirt("my shirt3", Color.BLACK)
            )
            val threads: ArrayList<Thread> = arrayListOf()

            for (i in 0..3) {

                val thread = Thread {
                    val instance = ShoppingCartClassic.get()
                    instance.addOutFit(outfit1)
                    instance.addOutFit(outfit2)
                    instance.addOutFit(outfit3)


                    instance.checkout().forEach {
                        println(it.shirt.style)
                    }
                }
                threads.add(thread)
            }
            threads.forEach (Thread::start)
        }
    }

and this is my result

my shirt1
my shirt1
my shirt2
my shirt3
my shirt1
my shirt2
my shirt3
my shirt1
my shirt2
my shirt3
my shirt2
my shirt3
....

**I also tested ** and I got this errors

Exception in thread "Thread-1" Exception in thread "Thread-3" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at dp.sigleton.Main$Companion.main$lambda-1(Main.kt:51)
	at java.base/java.lang.Thread.run(Thread.java:844)
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 }

        }
}

Solution 13 - Android

I'm new to Kotlin development, so I wanted the simplest solution but one that also resembles Java Singleton as much as possible. Double Checking for thread safety, Private Constructor, volatile reference. Below code worked for me best. Sharing it here in case someone else needs it.

class InstrumentationManager private constructor(prodToken: String, intToken: String) {
companion object {
    @Volatile
    private var INSTANCE: InstrumentationManager? = null
    fun getInstance(prodToken: String, intToken: String): InstrumentationManager =
        INSTANCE ?: synchronized(this) {
            INSTANCE ?: InstrumentationManager(prodToken, intToken).also { INSTANCE = it }
    }
}

}

> Description > > - private constructor --> private InstrumentationManager() > - InstrumentationManager? --> @Nullable > - INSTANCE ?: --> if(instance == null) { } > - InstrumentationManager(prodToken, intToken).also --> Extra processing once InstrumentationManager object is created.

Solution 14 - Android

Singletons

Singletons are used often enough for a simpler way of creating them to exist. Instead of the usual static instance, getInstance() method and a private constructor, Kotlin uses the object notation. For consistency, object notation is also used to define static methods.

 object CommonApiConfig {
private var commonApiConfig: CommonApiConfig? = null
fun getInstance(): CommonApiConfig {
    if (null == commonApiConfig) {
        commonApiConfig = CommonApiConfig
       }
    return CommonApiConfig.commonApiConfig!!
   }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLordRaydenMKView Question on Stackoverflow
Solution 1 - AndroidfalView Answer on Stackoverflow
Solution 2 - AndroidaminographyView Answer on Stackoverflow
Solution 3 - AndroidvoddanView Answer on Stackoverflow
Solution 4 - AndroidYisus ThreepwoodView Answer on Stackoverflow
Solution 5 - AndroidLeandro Castillo BorjaView Answer on Stackoverflow
Solution 6 - AndroidGeorgiosView Answer on Stackoverflow
Solution 7 - AndroidLouis CADView Answer on Stackoverflow
Solution 8 - AndroidWilliam LeungView Answer on Stackoverflow
Solution 9 - AndroidBraian CoronelView Answer on Stackoverflow
Solution 10 - AndroidWilson TranView Answer on Stackoverflow
Solution 11 - AndroidPrasadView Answer on Stackoverflow
Solution 12 - AndroidNoe Adrian Acuña PradoView Answer on Stackoverflow
Solution 13 - AndroidArpit RatanView Answer on Stackoverflow
Solution 14 - AndroidEmran HamzaView Answer on Stackoverflow