33

The Kotlin reference says that I can create a singleton using the object keyword like so:

object DataProviderManager {
  fun registerDataProvider(provider: DataProvider) {
    //
  }
}

However, I would like to pass an argument to that object. For example an ApplicationContext in an Android project.

Is there a way to do this?

jakk
  • 1,193
  • 2
  • 12
  • 28

6 Answers6

14

Since objects do not have constructors what I have done the following to inject the values on an initial setup. You can call the function whatever you want and it can be called at any time to modify the value (or reconstruct the singleton based on your needs).

object Singleton {
    private var myData: String = ""

    fun init(data: String)  {
        myData = data
    }

    fun singletonDemo() {
        System.out.println("Singleton Data: ${myData}")
    }
}
Jeremy Lyman
  • 3,084
  • 1
  • 27
  • 35
  • 19
    There are several big disadvantages to this answer. The first is that the caller can forget to invoke `init`. The second is that argument is stored in a `var` meaning it can be changed by mistake later, usually in a singleton you would not want something required during construction to be modified later. The third is that this answer doesn't even use method chaining which makes it a pain to use. – satur9nine Aug 11 '19 at 18:38
  • This answer is not the best practice to the question, As @satur9nine said – Ehsan Feb 01 '21 at 12:42
7

Kotlin has a feature called Operator overloading, letting you pass arguments directly to an object.

object DataProviderManager {
  fun registerDataProvider(provider: String) {
      //
  }

  operator fun invoke(context: ApplicationContext): DataProviderManager {
      //...
      return this
  }
}

//...
val myManager: DataProviderManager = DataProviderManager(someContext)
breandan
  • 1,965
  • 26
  • 45
  • This does the same as Jeremy's solution, but with different syntax. The singleton is still created without any arguments. – jakk Nov 26 '16 at 21:08
  • If you want it to work like a constructor, you can simply return `this`. I've updated the answer demonstrating that. – breandan Nov 27 '16 at 19:24
  • 5
    Ok, but what prevents you from using the object without ever calling the `invoke` method? – jakk Nov 28 '16 at 09:41
6

With most of the existing answers it's possible to access the class members without having initialized the singleton first. Here's a thread-safe sample that ensures that a single instance is created before accessing any of its members.

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

    fun printParam() {
        print("Param: $param")    
    }
}

Usage:

MySingleton.getInstance("something").printParam()
Georgios
  • 4,764
  • 35
  • 48
  • This is fine, but the main reason for using `object` would be to eliminate this synchronized singleton creation code. – jakk Oct 30 '19 at 14:23
3

There are also two native Kotlin injection libraries that are quite easy to use, and have other forms of singletons including per thread, key based, etc. Not sure if is in context of your question, but here are links to both:

Typically in Android people are using a library like this, or Dagger, et al to accomplish parameterizing singletons, scoping them, etc.

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
1

I recommend that you use this form to pass arguments in a singleton in Kotlin debit that the object your constructor is deprived and blocked:

object Singleton {

    fun instance(context: Context): Singleton {
        return this
    }

    fun SaveData() {}
}

and you call it this way in the activity

Singleton.instance(this).SaveData()
Mehmed
  • 2,880
  • 4
  • 41
  • 62
  • 1
    No, this example is not correct as you can still just call `Singleton.SaveData()` before initializing. You can also change the parameters you inject at anytime, which is not what a singleton would be able to do - unless that is specifically desired. – Jeff Padgett May 23 '21 at 16:05
0

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.

Wilson Tran
  • 4,050
  • 3
  • 22
  • 31