Using room as singleton in kotlin

后端 未结 5 1241
被撕碎了的回忆
被撕碎了的回忆 2021-02-01 08:24

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 = ar         


        
相关标签:
5条回答
  • 2021-02-01 09:05

    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)
    }
    
    0 讨论(0)
  • 2021-02-01 09:09

    I found a solution so here is the answer for future me and any one who might have the same problem.


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

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

    so I considered implementing one of them, but this doesn't feel right in kotlin too much boilerplate code :p


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

    my code becomes like this:

    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
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-01 09:13

    Here's how i figured out...

    @Database(entities = [MyEntity::class], version = dbVersion, exportSchema = true)
    abstract class AppDB : RoomDatabase() {
    
    // First create a companion object with getInstance method
        companion object {
            fun getInstance(context: Context): AppDB = 
        Room.databaseBuilder(context.applicationContext, AppDB::class.java, dbName).build()
        }
    
        abstract fun getMyEntityDao(): MyEntityDao
    }
    
    // This is the Singleton class that holds the AppDB instance 
    // which make the AppDB singleton indirectly
    // Get the AppDB instance via AppDBProvider through out the app
    object AppDBProvider {
    
    private var AppDB: AppDB? = null
    
        fun getInstance(context: Context): AppDB {
            if (appDB == null) {
                appDB = AppDB.getInstance(context)
            }
           return appDB!!
        }
    
    }
    
    0 讨论(0)
  • 2021-02-01 09:15

    singleton in kotlin is real easy just do this

    companion object {
        @JvmStatic
        val DATABASE_NAME = "DataBase"
    
        @JvmField
        val database = Room.databaseBuilder(App.context(), DataBase::class.java, DataBase.DATABASE_NAME).build()
    
    }
    
    0 讨论(0)
  • 2021-02-01 09:22

    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
    
    0 讨论(0)
提交回复
热议问题