Использование комнаты в качестве одноэлементного в kotlin

Я пытаюсь использовать Room как singleton, поэтому мне не приходилось ссылаться на Room.databaseBuilder() что дорого – не один раз.

 @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() } } 

Заметка:

  1. Нельзя использовать Object, потому что Room требует использования abstract class .
  2. singleton должен быть потокобезопасным, поскольку одновременно может обращаться к нему через несколько потоков.
  3. должен иметь возможность принимать Context в качестве аргумента.

Я просмотрел все похожие вопросы StackOverflow, и ни один из них не удовлетворяет моим требованиям

Синглтон с аргументом в Котлине не является потокобезопасным

Kotlin – Лучший способ конвертировать Singleton DatabaseController в Android не является потокобезопасным

Kotlin thread save native lazy singleton с параметром использует объект

Я нашел решение, так что вот ответ для будущего меня и любого, у кого может быть такая же проблема.


После некоторых исследований я обнаружил, что у меня есть два варианта.

  1. использование Блокировка с двойным контролем
  2. использовать Инициализацию по требованию владельца идиомы

поэтому я решил реализовать один из них, но это не очень хорошо в котлине слишком много шаблонов: p


поэтому после большего количества исследований я наткнулся на эту замечательную статью, которая обеспечивает отличное решение, которое использует блокировку с двойной проверкой, но с достаточной степенью точности.

мой код выглядит следующим образом:

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

из статьи:

Многоразовая реализация Kotlin:

Мы можем инкапсулировать логику, чтобы лениво создать и инициализировать одноэлемент с аргументом внутри класса SingletonHolder . Чтобы сделать эту логическую потокобезопасность, нам нужно реализовать синхронизированный алгоритм, а самый эффективный – который также является самым трудным для правильного выбора – это алгоритм блокировки с двойной проверкой.

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

Дополнительно: если вы хотите Singleton с двумя аргументами

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

Вы можете использовать стандартную библиотеку Kotlin

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

Лично я бы обычно добавлял в приложение приложения зависящие от ApplicationContext синглтоны, например

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

Вы даже можете определить метод расширения для легкого доступа как context.database .

 val Context.database get() = generateSequence(applicationContext) { (it as? ContextWrapper)?.baseContext }.filterIsInstance<MyApplication>().first().database 

В этом конкретном случае я бы посоветовал использовать Dagger 2 или какую-то другую библиотеку инъекций зависимости, такую ​​как Koin или Toothpick . Все три библиотеки позволяют обеспечить зависимость как одноточие.

Вот код для модуля 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) } 

Класс применения для обеспечения инъекции:

 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() } } 

А затем добавьте свою базу данных в качестве одноэлементного кода туда, где вам это нужно (например, в репозиторий вашего приложения):

 @Inject lateinit var mDb: AppDatabase init { App.component.inject(this) } 
Intereting Posts