web-dev-qa-db-fra.com

Utiliser la pièce comme singleton dans kotlin

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:

  1. Impossible d'utiliser Object car Room nécessite l'utilisation de abstract class.
  2. singleton doit être thread-safe car plusieurs threads peuvent y accéder en même temps.
  3. doit pouvoir prendre 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

7
humazed

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.

  1. utiliser Verrouillage à double contrôle
  2. use Idiome de titulaire d'initialisation à la demande

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

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)
}
3
Jan Slominski

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
1
ephemient

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

}
0
Rahul Kumar