Using room as singleton in kotlin

In this particular case I would resort to using Dagger 2, or some other dependency injection library like Koin or Toothpick. All three libraries allow to provide dependancies as singletons.

Here's the code for Dagger 2 module:

@Module
class AppModule constructor(private val context: Context) {
    @Provides
    @Singleton
    fun providesDatabase(): AppDatabase {
        return Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "train.db")
                .build()
    }
}

AppComponent:

@Singleton
@Component(modules = arrayOf(
        AppModule::class
))
interface AppComponent {
    fun inject(viewModel: YourViewModel)
    fun inject(repository: YourRepository)
}

Application class to provide injection:

class App : Application() {
    companion object {
        private lateinit var appComponent: AppComponent
        val component: AppComponent get() = appComponent
    }

    override fun onCreate() {
        super.onCreate()
        initializeDagger()
    }

    private fun initializeDagger() {
        component = DaggerAppComponent.builder()
                .appModule(AppModule(this))
                .build()
    }
}

And then inject your database as singleton to wherever you need it (for example in your app's repository):

@Inject lateinit var appDatabase: AppDatabase

init {
    App.component.inject(this)
}

After some research, I found that I have two options.

  1. Double-checked locking
  2. Initialization-on-demand holder idiom

I considered implementing one of them, but this didn't felt right for Kotlin - too much boilerplate code.


After more research, I stumbled upon this great article which provides an excellent solution, which uses Double-checked locking but in an elegant way.

companion object : SingletonHolder<AppDatabase, Context>({
       Room.databaseBuilder(it.applicationContext, AppDatabase::class.java, "train.db").build()
})

From the article:

A reusable Kotlin implementation:

We can encapsulate the logic to lazily create and initialize a singleton with argument inside a SingletonHolder class. In order to make that logic thread-safe, we need to implement a synchronized algorithm and the most efficient one — which is also the hardest to get right — is the double-checked locking algorithm.

open class SingletonHolder<T, A>(creator: (A) -> T) {
    private var creator: ((A) -> T)? = creator
    @Volatile private var instance: T? = null

    fun getInstance(arg: A): T {
        val i = instance
        if (i != null) {
            return i
        }

        return synchronized(this) {
            val i2 = instance
            if (i2 != null) {
                i2
            } else {
                val created = creator!!(arg)
                instance = created
                creator = null
                created
            }
        }
    }
}

Extra: if you want Singleton with two arguments

open class SingletonHolder2<out T, in A, in B>(creator: (A, B) -> T) {
    private var creator: ((A, B) -> T)? = creator
    @Volatile private var instance: T? = null

    fun getInstance(arg0: A, arg1: B): T {
        val i = instance
        if (i != null) return i

        return synchronized(this) {
            val i2 = instance
            if (i2 != null) {
                i2
            } else {
                val created = creator!!(arg0, arg1)
                instance = created
                creator = null
                created
            }
        }
    }
}

Used @Volatile for thread safety.

public abstract class AppDatabase : RoomDatabase() {

   abstract fun trainDao(): trainDao

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

        fun getDatabase(context: Context): Db = INSTANCE ?: synchronized(this){
            val instance = Room.databaseBuilder(
            context.applicationContext,
            AppDatabase ::class.java,
            "train-db"
          ).build()
          INSTANCE = instance
          instance
        }
   }
}

taken from : https://developer.android.com/codelabs/android-room-with-a-view-kotlin#7