等待Room @Insert查询完成

时间:2019-11-26 17:26:21

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

我不知道如何使用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()
})

ViewModel

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

RoomDao

@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循环中的代码)执行插入。问题似乎在于执行此嵌套循环会破坏操作的“同步性”

2 个答案:

答案 0 :(得分: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() }
                )
            }
        }
    }
}

答案 1 :(得分:0)

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

使用回调或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