首页 > 解决方案 > Android Kotlin : Room with corountines : 如何在更新 UI 之前创建 db、预置数据和从 db 值填充地图

问题描述

我正在 Kotlin 中编写一个应用程序,它需要在应用程序安装/首次启动时使用播种到数据库中的数据。我想使用Room进行数据库访问和使用协程进行异步执行。

我看到很多关于如何使用 Room 的例子和很多关于如何使用 corountines 的例子。但是我没有看到任何同时使用两者的例子。

我需要创建一个关于应用程序安装和种子数据的数据库,然后立即从该数据库中读取,以获得可以在应用程序中使用的缓存值映射。我不明白如何在主线程上而不是一起进行创建/种子/读取。

稍后在应用程序中,我需要清空值映射并再次查询数据库以重新填充映射,然后从映射中的新值更新 UI。

我的申请的背景信息

我的应用程序将根据用户选择的两个选项向用户显示一组图像。这两个选项作为数字存储在首选项存储中,可以是 1 或 0。

因此,如果用户选择了“option1=0”和“option2=1”并且我的数据库包含如下数据

id   option1  option2  letter    image
------------------------------------------------
1        0        0       A      image_a_0_0.png
2        0        0       B      image_b_0_0.png
3        0        0       C      image_c_0_0.png
4        0        0       D      image_d_0_0.png
5        1        0       A      image_a_1_0.png
6        1        0       B      image_b_1_0.png
7        1        0       C      image_c_1_0.png
8        1        0       D      image_d_1_0.png

9        0        1       A      image_a_0_1.png
10       0        1       B      image_b_0_1.png
11       0        1       C      image_c_0_1.png
12       0        1       D      image_d_0_1.png

13       1        1       A      image_a_1_1.png
14       1        1       B      image_b_1_1.png
15       1        1       C      image_c_1_1.png
16       1        1       D      image_d_1_1.png

然后将向用户显示以下图像:

image_a_0_1.png
image_b_0_1.png
image_c_0_1.png
image_d_0_1.png

我的要求

在我的 Room 数据库代码中,我的数据库实例有一个单例,如果数据库不存在,我会创建它。我使用回调来了解数据库的创建时间。在此回调中,我使用 corountine 调用代码将数据播种到数据库中。

我房间的数据库代码

@Database(....)
abstract class AppDatabase : RoomDatabase() {

    abstract fun myDao(): MyDao

    companion object {

        private const val DATABASE_NAME = "my_databse"

        // Singleton prevents multiple instances of database opening at the same time.
        @Volatile
        private var sINSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            val tempInstance = sINSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java,
                        DATABASE_NAME
                )
                .addCallback(seedDatabaseCallback(context))
                .build()

                sINSTANCE = instance
                return instance
            }
        }

        /**
         * Callback which is called when the Database is first created.
         */
        private fun seedDatabaseCallback(context: Context): Callback {
            return object : Callback() {
                override fun onCreate(db: SupportSQLiteDatabase) {
                    super.onCreate(db)
                    seedData(context)
                }
            }
        }

        suspend fun seedData(context:Context){
            val db = AppDatabase.getInstance(context)
            var myDao = db.myDao()
            myDao.insert(..........)
            .....
        }
    }

}

我有一个“DataProvider.kt”文件,它负责从首选项存储和数据库(使用 DAO)读取值并更新应用程序其余部分中使用的映射。当用户更改两个选项中的任何一个时,可以在我的应用程序中随时调用此方法,以便地图始终包含适用于用户当前设置的正确图像

suspend fun updateMaps(context: Context) {
    val mSharedPref = context.getSharedPreferences(OPTIONS_PREFERENCES_KEY, Context.MODE_PRIVATE)
    val option1 = mSharedPref.getLong("option1_PREF", 0)
    val option2 = mSharedPref.getLong("option2_PREF", 0)

    updateMaps(context, option1, option2)
}

private suspend fun updateMaps(context: Context, option1 : Int, option2 : Int) {
    withContext(Dispatchers.IO) {
        var db = AppDatabase.getInstance(context)
        var myValues = db.myDao().getValues(option1, option2)
        for (value in myValues){
            myMap[value .letter] = value.image
        }
    }
}

我的问题

在我的主要活动的 onCreate 方法中,我试图在设置地图之前检查数据库是否存在(如果需要,创建和播种数据)。如果地图有值,我会设置在我的主要活动上可见的视图。

我不完全了解如何将 Room 和 Corountines 放在一起。我在应用程序安装中发现,在我的主要活动中,它调用了 updateMaps 并创建了数据库并播种了数据,但它从未将我的视图设置为可见,因为在签入主要活动时地图没有值。

我“认为”这是因为数据库创建是在一个线程上播种的,然后数据库值是红色的,并且映射在另一个线程上更新,因此它们彼此不依赖。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var myView = findViewById<CardView>(R.id.myView)
    ....
    ....

    GlobalScope.launch {
        // Set up the maps, this will create the database and seed the factory
        // data the first time this application is run
        DataProvider.updateMaps(this@DashboardActivity)
        if (DataProvider.myMap.isNotEmpty()) {
            // ISSUE DOES NOT COME IN HERE
            myView.visibility = View.VISIBLE
        }
    }

    ....
}

我已经看到关于 async() 和 await() 的评论,但我不完全清楚你什么时候会使用它们而不是我在这个问题中的代码。

标签: androiddatabasekotlin

解决方案


在 Google 的 Room with a View 课程中有一个使用协程预填充数据库的示例,只有它会在每次打开时重新填充

https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/index.html?index=..%2F..index#11

编辑 - 在阅读了更多详细信息后,我认为如果您查看示例 Sunflower 应用程序会更好

https://github.com/android/sunflower

它使用种子工作者基于 JSON 文件填充数据库

class SeedDatabaseWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
    override suspend fun doWork(): Result = coroutineScope {
        try {
            applicationContext.assets.open(PLANT_DATA_FILENAME).use { inputStream ->
                JsonReader(inputStream.reader()).use { jsonReader ->
                    val plantType = object : TypeToken<List<Plant>>() {}.type
                    val plantList: List<Plant> = Gson().fromJson(jsonReader, plantType)

                    val database = AppDatabase.getInstance(applicationContext)
                    database.plantDao().insertAll(plantList)

                    Result.success()
                }
            }
        } catch (ex: Exception) {
            Log.e(TAG, "Error seeding database", ex)
            Result.failure()
        }
    }

推荐阅读