web-dev-qa-db-ja.com

Kotlinでシングルトンとして部屋を使用する

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

注意:

  1. Roomではabstract classを使用する必要があるため、オブジェクトを使用できません。
  2. シングルトンは、複数のスレッドが同時にアクセスする可能性があるため、スレッドセーフでなければなりません。
  3. Contextを引数として取ることができる必要があります。

同様のStackOverflowの質問をすべて調べましたが、どれも私の要件を満たしていません

Kotlinで引数を持つシングルトンはスレッドセーフではありません

Kotlin-Androidでシングルトンデータベースコントローラーを変換する最良の方法はスレッドセーフではありません

Kotlinスレッドはパラメーター付きのネイティブレイジーシングルトンを保存しますオブジェクトを使用します

10
humazed

私は解決策を見つけたので、ここに将来の私と同じ問題を抱えている可能性のある人のための答えがあります。


調査の結果、2つの選択肢があることがわかりました。

  1. 使用 ダブルチェックロック
  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
            }
        }
    }
}
10
humazed

この特定のケースでは、 Dagger 2 、または KoinToothpick などの他の依存関係注入ライブラリを使用することに頼っています。 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)
}
4
Jan Slominski

Kotlin標準ライブラリの

fun <T>  lazyLazyThreadSafetyMode 。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
1
ephemient

ここに私が理解した方法があります...

@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