【问题标题】:Wait for Room @Insert query completion等待 Room @Insert 查询完成
【发布时间】:2020-03-22 04:38:56
【问题描述】:

我不知道如何使用 Room 和 MVVM 模式进行“简单”操作。 我正在使用 Retrofit 获取一些数据。 “正确”的响应会触发活动中的观察者,并且响应本身的一小部分使用 Room 库插入到数据库中,擦除所有先前存储的值并插入新的值。否则旧值将保留在 DB 上。 之后,我想检查数据库中的某个字段,但我无法强制此操作等到前一个完成。

型号

@Entity(tableName = "licence")
data class Licence(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "licence_id")
    var licenceId: Int = 0,
    @Ignore
    var config: List<LicenceConfig>? = null,
.......
//all the others attributes )

@Entity(foreignKeys = [
ForeignKey(
        entity = Licence::class,
        parentColumns = ["licence_id"],
        childColumns = ["licence_reference"],
        onDelete = ForeignKey.CASCADE
)],tableName = "licence_configurations")
data class LicenceConfig(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "licence_config_id")
    var licenceConfigId: Int,

    @ColumnInfo(name="licence_reference")
    var licenceReference: Int,

活动中的观察者

loginViewModel.apiResponse.observe(this, Observer { response ->
        response?.let {
             loginViewModel.insertLicences(response.licence)
        }
           //here I need to wait for the insertion to end
           loginViewModel.methodToCheckForTheFieldOnDatabase()
})

视图模型

fun insertLicences(licences: List<Licence>) = viewModelScope.launch {
    roomRepository.deleteAllLicences()
    licences.forEach { licence ->
        roomRepository.insertLicence(licence).also { insertedLicenceId ->
            licence.config?.forEach { licenceConfiguration ->
                roomRepository.insertLicenceConfiguration(
                        licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
                )
            }
        }
    }
}

房间存储库

class RoomRepository(private val roomDao: RoomDao) {

val allLicences: LiveData<List<Licence>> = roomDao.getAllLicences()

suspend fun insertLicence(licence: Licence): Long {
   return roomDao.insertLicence(licence)
   }

suspend fun insertLicenceConfiguration(licenceConfiguration: LicenceConfig){
    return roomDao.insertLicenceConfiguration(LicenceConfig)
   }
}

房间道

@Dao
interface RoomDao {

@Query("select * from licence")
fun getAllLicences(): LiveData<List<Licence>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLicence(licence: Licence): Long

@Insert
suspend fun insertLicenceConfiguration(licence: LicenceConfig)

@Query("DELETE FROM licence")
suspend fun deleteAllLicences()
}

将观察者设置为“allLicences”LiveData 或直接在 DB 上的该字段上不是一个选项,因为操作将在活动创建后立即执行,我必须等到 API 响应才能执行它们。

在另一个没有 Room 的项目中,我使用 async{} 和 .await() 在使用协程时执行顺序操作,但我无法真正让它在这里工作。当我在插入方法之后暂停调试器时,“allLicences”的值始终为空,但在恢复和导出数据库后,数据被正确插入。我还尝试在 ViewModel 方法之后添加 .invokeOnCompletion{},但结果相同。
基本上我想等待这个方法结束来做另一个操作。

有什么建议吗?

编辑

我完全忘了报告模型!每个许可证都有一个配置列表。当我执行许可证插入时,我采用自动生成的 ID,将其应用于 licenceConfig,然后为每个 licenceConfig 对象(ViewModel 方法的嵌套 forEach 循环中的代码)执行插入。问题似乎是执行这个嵌套循环破坏了操作的“同步性”

【问题讨论】:

    标签: android kotlin mvvm android-room kotlin-coroutines


    【解决方案1】:

    要等到插入完成,您需要将协程创建从insertLicences() 移动到您的观察者,并让insertLicences() 成为挂起函数。


    loginViewModel.apiResponse.observe(this, Observer { response ->
        lifecycleScope.launch { 
            response?.let {
                 loginViewModel.insertLicences(response.licence)
            }
            //here I need to wait for the insertion to end
            loginViewModel.methodToCheckForTheFieldOnDatabase()
        }
    })
    

    suspend fun insertLicences(licences: List<Licence>) {
        roomRepository.deleteAllLicences()
        licences.forEach { licence ->
            roomRepository.insertLicence(licence).also { insertedLicenceId ->
                licence.config?.forEach { licenceConfiguration ->
                    roomRepository.insertLicenceConfiguration(
                        licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
                    )
                }
            }
        }
    }
    

    替代解决方案

    您可以将观察者中存在的所有代码转移到 ViewModel 中。

    loginViewModel.apiResponse.observe(this, Observer { response ->
        loginViewModel.refreshLicenses(response)
    })
    

    在 ViewModel 中

    fun refreshLicenses(response:Response?){
        viewModelScope.launch{
            response?.let {
                insertLicences(response.licence)
            }
            methodToCheckForTheFieldOnDatabase()
        }
    }
    

    并将 insertLicences 设为挂起函数

    suspend fun insertLicences(licences: List<Licence>) {
        roomRepository.deleteAllLicences()
        licences.forEach { licence ->
            roomRepository.insertLicence(licence).also { insertedLicenceId ->
                licence.config?.forEach { licenceConfiguration ->
                    roomRepository.insertLicenceConfiguration(
                        licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
                    )
                }
            }
        }
    }
    

    【讨论】:

    • 感谢您的回复!我目前正在尝试“替代”解决方案,因为它与我在另一个项目中成功实施的解决方案相同。它仍然不起作用,但我发现了问题。如果我只留下 roomRepository.insertLicence(licence) ,则在第一个完成之前不会执行检查方法(我在 forEach 之后添加了 5 秒延迟以确保)。所以问题似乎是所有 licenceConfigurations 的嵌套插入,它“打破了流程”。我已经用更多信息编辑了答案
    • (5) 语句在 2 处中止:[PRAGMA journal_mode=PERSIST]
    【解决方案2】:

    编辑:在我回复之前没有阅读你的结论但是,我仍然认为你的答案在于协程

    使用回调或promise,插入查询完成后不会执行你的函数吗?

    回调

    使用回调,想法是将一个函数作为参数传递给 另一个函数,并在进程完成后调用这个函数 完成。

    fun postItem(item: Item) {
        preparePostAsync { token -> 
            submitPostAsync(token, item) { post -> 
                processPost(post)
            }
        }
    }
    
    fun preparePostAsync(callback: (Token) -> Unit) {
        // make request and return immediately 
        // arrange callback to be invoked later
    }
    

    我希望承诺是诚实的

    承诺

    期货或承诺背后的想法(还有其他术语 可以根据语言/平台引用),是当我们 打个电话,我们承诺在某个时候它会返回一个 称为 Promise 的对象,然后可以对其进行操作。

    fun postItem(item: Item) {
        preparePostAsync() 
            .thenCompose { token -> 
                submitPostAsync(token, item)
            }
            .thenAccept { post -> 
                processPost(post)
            }
    
    }
    
    fun preparePostAsync(): Promise<Token> {
        // makes request an returns a promise that is completed later
        return promise 
    }
    

    做好你的工作,当承诺履行完毕后,继续进行数据验证。

    您可以阅读有关协程的更多信息here

    【讨论】:

    • 您好,乔治,感谢您的回复!对于另一个项目,我完全遵循了阅读协程的最后一个示例,并且效果很好!有没有办法让它在这种情况下也可以工作,或者你认为最好尝试一下承诺?
    • 我会信守承诺。正如您在编辑时所说,您正在等待功能完成以便继续处理生成的数据。看看上面的答案,但我很确定诺言会让你的生活更轻松
    猜你喜欢
    • 1970-01-01
    • 2021-03-23
    • 2022-01-21
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2018-11-22
    • 2016-11-19
    • 1970-01-01
    相关资源
    最近更新 更多