How to simplify MVVM Repository function using Room and Retrofit

Below is ViewModel class and Repository class of Android MVVM design pattern.
(1) ViewModel calls a function of Repository in Coroutine.
(2) Then Repository checkes data in SQLite.
(3) If there is no data in SQLite, request data using Retrofit.
(4) After retrieve data from Retrofit, the data is saved in SQLite and sent to ViewModel.

It’s working correctly, just it looks the hierarchy of coroutine is complicated.(In StateRepository.reqStates())
When I remove coroutine in Repository, SQLite does not work.
If this code can be more simple, please let me know.
Thank you.


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

Creating a CoroutineScope is generally considered a bad practice – instead, you want to embrace the idea of structured concurrency – that every coroutine is structured in a way that has a clear parent/child relationship.

Similarly, you want your UI to be reactive to changes – changes in the database should be reflected in the UI directly. That means your Room database should be returning observable queries – a Flow<List<Data>> or LiveData<List<Data>> rather than just a single List<Data> that doesn’t change over time as the database changes.

This means there’s a few changes you should make on your StateDao and StateApi:

  1. Your StateDao should return a Flow<List<State>> so that it automatically sends updates as your database changes:
@Dao
interface StateDao{
    @Query("SELECT * FROM states")
    fun getStates(): Flow<List<State>>
  1. Your StateApi should be using Retrofit’s support for suspend methods instead of using a Call.
@GET("states")
suspend fun states(): List<State>

This allows us to rewrite your code as:

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

Generally, you’d want to Schedule network calls with WorkManager so that they are automatically retried even if the network is down, but that would involve just scheduling the work in the isEmpty() block and moving the StateApi call into the Worker and can be done as a separate step.

Recognized by Mobile Development Collective

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật