J'essaie d'utiliser Room comme singleton afin de ne pas avoir à invoquer Room.databaseBuilder()
(ce qui coûte cher) plus d'une fois.
@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()
}
}
Remarque:
abstract class
.Context
comme argument.J'ai examiné toutes les questions similaires de StackOverflow et aucune d'entre elles ne répond à mes exigences.
Singleton avec argument en Kotlin n'est pas thread-safe
Kotlin - Meilleur moyen de convertir Singleton DatabaseController sous Android n'est pas thread-safe
Le thread Kotlin enregistre le singleton paresseux natif avec le paramètre utilise l'objet
J'ai trouvé une solution alors voici la réponse pour moi et tous ceux qui pourraient avoir le même problème.
Après quelques recherches, j'ai découvert que j'avais deux options.
j'ai donc envisagé de mettre en œuvre l'un d'entre eux, mais cela ne semble pas correct dans kotlin: trop de code passe-partout: p
donc, après plus de recherches, je suis tombé sur ce superbe article qui fournit une excellente solution qui utilise le verrouillage à double contrôle mais de manière éligante.
mon code devient comme ceci:
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it, AppDatabase::class.Java, "train.db").build()
})
extrait de l'article:
Une implémentation Kotlin réutilisable:
Nous pouvons encapsuler la logique pour Créer et initialiser un singleton avec un argument dans une classe
SingletonHolder
. Afin de rendre cette logique thread-safe, nous Devons implémenter un algorithme synchronisé et le plus efficace - qui est aussi le plus difficile à obtenir - est le double contrôle algorithme de verrouillage.
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: Si vous voulez Singleton avec deux 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
}
}
}
}
Dans ce cas particulier, je préférerais utiliser Dagger 2 , ou une autre bibliothèque d'injection de dépendances telle que Koin ou Cure-dents . Les trois bibliothèques permettent de fournir des dépendances en tant que singletons.
Voici le code du module Dagger 2:
@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)
}
Classe d'application permettant l'injection:
class App : Application() {
companion object {
private lateinit var mComponent: AppComponent
val component: AppComponent get() = mComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
mComponent = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
Et ensuite, injectez votre base de données sous forme de singleton à l'endroit où vous en avez besoin (par exemple, dans le référentiel de votre application):
@Inject lateinit var mDb: AppDatabase
init {
App.component.inject(this)
}
Vous pouvez utiliser la bibliothèque standard Kotlin
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
}
}
Personnellement, j’ajouterais normalement des singletons dépendants de ApplicationContext dans l’application, par exemple.
<!-- AndroidManifest.xml -->
<manifest>
<application Android:name="MyApplication">
...
class MyApplication : Application() {
val database: AppDatabase by lazy {
Room.databaseBuilder(this, AppDatabase::class.Java, "train.db").build()
}
}
Vous pouvez même définir une méthode d'extension pour un accès facile en tant que context.database
.
val Context.database
get() =
generateSequence(applicationContext) {
(it as? ContextWrapper)?.baseContext
}.filterIsInstance<MyApplication>().first().database
Voici comment j'ai compris ...
@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!!
}
}