Nguyễn Văn Quang
Nguyễn Văn Quang

Reputation: 33

liveData with coroutines only trigger first time

I have a usecase:

  1. Open app + disable network -> display error
  2. Exit app, then enable network, then open app again

Expected: app load data

Actual: app display error that meaning state error cached, liveData is not emit

Repository class

    class CategoryRepository(
    private val api: ApiService,
    private val dao: CategoryDao
) {
    val categories: LiveData<Resource<List<Category>>> = liveData {
        emit(Resource.loading(null))

        try {
            val data = api.getCategories().result
            dao.insert(data)

            emit(Resource.success(data))
        } catch (e: Exception) {

            val data = dao.getCategories().value

            if (!data.isNullOrEmpty()) {
                emit(Resource.success(data))
            } else {
                val ex = handleException(e)
                emit(Resource.error(ex, null))
            }
        }
    }
}

ViewModel class

class CategoryListViewModel(
    private val repository: CategoryRepository
): ViewModel() {

    val categories = repository.categories
}

Fragment class where LiveDate obsever

viewModel.apply {
            categories.observe(viewLifecycleOwner, Observer {
                // live data only trigger first time, when exit app then open again, live data not trigger
            })
        }

can you help me explain why live data not trigger in this usecase and how to fix? Thankyou so much

Update

I have resolved the above problem by replace val categories by func categories() at repository class. However, I don't understand and can't explain why it works properly with func but not val.

Upvotes: 0

Views: 410

Answers (1)

apksherlock
apksherlock

Reputation: 8371

Why does this happen? This happens because your ViewModel has not been killed yet. The ViewModel on cleared() is called when the Fragment is destroyed. In your case your app is not killed and LiveData would just emit the latest event already set. I don't think this is a case to use liveData builder. Just execute the method in the ViewModel when your Fragment gets in onResume():

override fun onResume(){
  viewModel.checkData()
  super.onResume()
}
// in the viewmodel
fun checkData(){
   _yourMutableLiveData.value = Resource.loading(null)

        try {
            val data = repository.getCategories()
            repository.insert(data)

            _yourMutableLiveData.value = Resource.success(data)
        } catch (e: Exception) {

            val data = repository.getCategories()

            if (!data.isNullOrEmpty()) {
                _yourMutableLiveData.value = Resource.success(data)
            } else {
                val ex = handleException(e)
                _yourMutableLiveData.value = Resource.error(ex,null)
            }
        }
}

Not sure if that would work, but you can try to add the listener directly in onResume() but careful with the instantiation of the ViewModel.

Small advice, if you don't need a value like in Resource.loading(null) just use a sealed class with object

UPDATE Regarding your question that you ask why it works with a function and not with a variable, if you call that method in onResume it will get executed again. That's the difference. Check the Fragment or Activity lifecycle before jumping to the ViewModel stuff.

Upvotes: 1

Related Questions