Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using room as singleton in kotlin

I'm trying to use Room as singleton so I didn't have to invoke Room.databaseBuilder() -which is expensive- more than once.

@Database(entities = arrayOf(
        Price::class,
        StationOrder::class,
        TicketPrice::class,
        Train::class,
        TrainCategory::class
), version = 2)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

    abstract fun dao(): TrainDao

companion object {
        fun createDatabase(context: Context): AppDatabase
                = Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
    }
}

Note:

  1. Can't use Object because Room requires using abstract class.
  2. singleton must be thread safe because multiple threads might access it at the same time.
  3. must be able to take Context as an argument.

I have looked at all similar StackOverflow questions and none of them satisfy my requirements

Singleton with argument in Kotlin isn't thread-safe

Kotlin - Best way to convert Singleton DatabaseController in Android isn't thread-safe

Kotlin thread save native lazy singleton with parameter uses object

like image 500
humazed Avatar asked Aug 28 '17 06:08

humazed


People also ask

How can you create singleton in Kotlin?

If we especially conversation around Android, we know that in Android we by and large have to pass a context instance to init block of a singleton. We can do it by using Early initialization and Apathetic initialization. In early initialization, all the components are initialized within the Application.

Is Kotlin singleton thread safe?

The singleton initialization will be lazy and thread-safe. It's just an optimization to free some memory by setting the creator to null after initialization is complete.

Is companion object singleton Kotlin?

The companion object is an object (singleton) that companies (belongs to) a class. (this is an other way for implementing static methods in other languages).


4 Answers

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
            }
        }
    }
}
like image 97
humazed Avatar answered Sep 21 '22 09:09

humazed


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)
}
like image 38
Jan Slominski Avatar answered Sep 22 '22 09:09

Jan Slominski


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

like image 27
Chinthaka Fernando Avatar answered Sep 18 '22 09:09

Chinthaka Fernando


You could make use of the Kotlin standard library's

fun <T> lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer: () -> T): Lazy<T>
companion object {
    private lateinit var context: Context
    private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
    }
    fun getDatabase(context: Context): AppDatabase {
        this.context = context.applicationContext
        return database
    }
}

Personally though, I would normally add ApplicationContext-dependent singletons inside the Application, e.g.

<!-- AndroidManifest.xml -->
<manifest>
  <application android:name="MyApplication">
...
class MyApplication : Application() {
    val database: AppDatabase by lazy {
        Room.databaseBuilder(this, AppDatabase::class.java, "train.db").build()
    }
}

You can even define an extension method for easy access as context.database.

val Context.database
    get() =
        generateSequence(applicationContext) {
       (it as? ContextWrapper)?.baseContext
       }.filterIsInstance<MyApplication>().first().database
like image 29
ephemient Avatar answered Sep 19 '22 09:09

ephemient