Roomをシングルトンとして使用しようとしているので、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()
}
}
注意:
abstract class
を使用する必要があるため、オブジェクトを使用できません。Context
を引数として取ることができる必要があります。同様のStackOverflowの質問をすべて調べましたが、どれも私の要件を満たしていません
Kotlinで引数を持つシングルトンはスレッドセーフではありません
Kotlin-Androidでシングルトンデータベースコントローラーを変換する最良の方法はスレッドセーフではありません
Kotlinスレッドはパラメーター付きのネイティブレイジーシングルトンを保存しますオブジェクトを使用します
私は解決策を見つけたので、ここに将来の私と同じ問題を抱えている可能性のある人のための答えがあります。
調査の結果、2つの選択肢があることがわかりました。
そのため、そのうちの1つを実装することを検討しましたが、kotlinでは定型コードが多すぎるため、これは正しくありません。
したがって、さらに調査した後、私は偶然に偶然に遭遇しました この素晴らしい記事 は、ダブルチェックロックを使用する優れたソリューションを提供しますが、適格な方法です。
私のコードは次のようになります:
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it.applicationContext, 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
}
}
}
}
Extra:2つの引数を持つシングルトンが必要な場合
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
}
}
}
}
この特定のケースでは、 Dagger 2 、または Koin や Toothpick などの他の依存関係注入ライブラリを使用することに頼っています。 3つのライブラリはすべて、依存関係をシングルトンとして提供できます。
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 appComponent: AppComponent
val component: AppComponent get() = appComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
component = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
次に、データベースを必要な場所にシングルトンとして挿入します(たとえば、アプリの repository ):
@Inject lateinit var appDatabase: AppDatabase
init {
App.component.inject(this)
}
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
ここに私が理解した方法があります...
@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!!
}
}