下面是Android MVVM设计模式的ViewModel类和Repository类。
(1)ViewModel在Coroutine中调用Repository的一个函数。
(2) 然后Repository检查SQLite中的数据。
(3) 如果SQLite中没有数据,则使用Retrofit请求数据。
(4) 从Retrofit中检索数据后,将数据保存在SQLite中并发送到ViewModel。
它工作正常,只是看起来协程的层次结构很复杂。(在 StateRepository.reqStates() 中)
当我在存储库中删除协程时,SQLite 不起作用。
如果这段代码可以更简单,请告诉我。
谢谢你。
class WeatherViewModel @Inject constructor(private val stateRepository: StateRepository) : ViewModel() {
val ldWeather = MutableLiveData<Weather>()
// Request Card data list to Repository
fun reqStates() {
viewModelScope.launch {
val res = stateRepository.reqStates()
withContext(Dispatchers.Main) {
res?.let { ldStates.postValue(it) }
}
}
}
}
open class StateRepository @Inject constructor(var api: StateApi, private val db: StateDatabase) {
suspend fun reqStates(): List<State>? {
return suspendCoroutine { continuation ->
CoroutineScope(Dispatchers.IO).launch {
// Search State list in SQLite
val states = db.stateDao().getAll()
if(!states.isNullOrEmpty()) {
continuation.resume(states)
} else {
resSuspendCoroutine(continuation)
}
}
}
}
private fun resSuspendCoroutine(continuation: Continuation<List<State>?>) {
val call: Call<List<State>> = api.states()
// Request State list to server when SQLite is empty
call.enqueue(object : Callback<List<State>> {
override fun onResponse(call: Call<List<State>>, response: Response<List<State>>) {
continuation.resume(response.body())
// Save state list in SQLite
response.body()?.let { saveStatesInDB(it) }
}
override fun onFailure(call: Call<List<State>>, t: Throwable) {
continuation.resume(null)
}
})
}
// Save state list in SQLite
private fun saveStatesInDB(states: List<State>) {
CoroutineScope(Dispatchers.IO).launch {
states.forEach { db.stateDao().insert(it) }
}
}
}
创建
CoroutineScope
通常被认为是一种不好的做法 - 相反,您想要接受结构化并发的想法 - 每个协程都以具有明确的父/子关系的方式构建。
同样,您希望 UI 对更改做出反应 - 数据库中的更改应直接反映在 UI 中。这意味着您的 Room 数据库应该返回 可观察查询 -
Flow<List<Data>>
或 LiveData<List<Data>>
而不是仅仅返回一个 List<Data>
,它不会随着数据库的变化而变化。
这意味着您应该对
StateDao
和 StateApi
进行一些更改:
StateDao
应返回 Flow<List<State>>
,以便在数据库更改时自动发送更新:@Dao
interface StateDao{
@Query("SELECT * FROM states")
fun getStates(): Flow<List<State>>
StateApi
应该使用 Retrofit 对 suspend
方法的支持,而不是使用 Call
。@GET("states")
suspend fun states(): List<State>
这允许我们将您的代码重写为:
class WeatherViewModel @Inject constructor(
private val stateRepository: StateRepository
) : ViewModel() {
// Get your states as a Flow, converting to a LiveData is optional
val states: Flow<List<State>> = stateRepository.getStates().asLiveData()
}
open class StateRepository @Inject constructor(
var api: StateApi,
private val db: StateDatabase
) {
// Return a Flow that automatically sends new data
// as the database changes
fun getStates(): Flow<List<State>> =
db.stateDao().getStates().onEach { states ->
// When the database emits an empty set of data, we'll
// load from the network
if (states.isEmpty()) {
val newStates = api.states()
// By using NonCancellable, we'll ensure the entire set of
// data is added even if the user leaves the screen the
// ViewModel is tied to while this is still going
withContext(NonCancellable) {
newStates.forEach { db.stateDao().insert(it) }
}
}
}
}
通常,您希望使用 WorkManager 安排网络调用,以便即使网络关闭,它们也会自动重试,但这只涉及在 isEmpty()
块中安排工作并将
StateApi
调用移动到Worker 可以作为单独的步骤完成。