0

I'm a complete novice when it comes to LiveData and MVVM architecture. I'm trying to figure out how to observe a LiveData<List> in the ViewModel to update another variable depending on if it is empty or not.

I'm getting the LiveData from my Room database with this:

class MealsViewModel @Inject constructor(
    private val mealDao : MealDao
) : ViewModel() {

    ...

    private val currentDay: MutableLiveData<Date> = MutableLiveData(Date())
    val meals = Transformations.switchMap(currentDay){ date -> mealDao.getMeals(date).asLiveData() }

I would like for another variable in the ViewModel, private val empty: Boolean, to update anytime the list is returned empty (null). This will be used in updating an ImageView in the Fragment from Visible.GONE to Visible.VISIBLE.

How do I check if val meals is empty synchronously?

I've read around and saw some people said to useobserveForever, but the architecture guide explicitly advises against any observers in ViewModels.

I could probably observe the LiveData in the Fragment, but that would require business logic in the Fragment, ie:

viewModel.meals.observe(viewLifecycleOwner) {

    if meals.value.isEmpty() imageView.visibility = View.VISIBLE else imageView.visibility = View.GONE
}

And I'd like to keep the Fragment as 'dumb' as possible, so I'd prefer to have that logic in the ViewModel. Is that possible?

2 Answers 2

1

You can check live data meal to see if it's empty or null and then trigger with your live data empty like this:

In the viewmodel, you create a livedata isEmptyMeals. This live data variable will always trigger when meals value change and will check if your meals value are empty or null.

MealsViewModel.kt

class MealsViewModel @Inject constructor(
    private val mealDao : MealDao
) : ViewModel() {

    ...

    private val currentDay: MutableLiveData<Date> = MutableLiveData(Date())
    val meals = Transformations.switchMap(currentDay){ date -> mealDao.getMeals(date).asLiveData() }
    
    val isEmptyMeals = meals.map {
        it.isNullOrEmpty()
    }
}

And in the fragment, you will listen to observe the livedata isEmptyMeals and perform the logic to hide or show the image view you want.

Fragment.kt

viewModel.isEmptyMeals.observe(viewLifecycleOwner) {
    imageView.visibility = if (it) View.VISIBLE else View.GONE
}
Sign up to request clarification or add additional context in comments.

1 Comment

I moved if (it.isNullOrEmpty()) View.VISIBLE else View.GONE into the .map within the ViewModel, and it worked perfectly. The Fragment was nice and simple afterward with imageView.visibility = it. Thank you!
0

I don't know exactly how your code is set up but you can do something like below

Add variable to ViewModel

val empty = MutableLiveData<Boolean>()

In meals observer viewModel.meals.observe(viewLifecycleOwner) {

viewModel.empty,postValue(meals.value.isEmpty())

Then observe from empty

Using MediatorLiveData

In your ViewModel class, create

val empty = MediatorLiveData<Boolean>()

Then

empty.addSource(meals) {
    empty.value = it.isEmpty()
}

5 Comments

Interesting. So you're suggesting that I should observe the LiveData and update val empty in the ViewModel from the fragment? Would that violate the principles of MVVM at all?
In your case, it is easier to do so. You can also use MediatorLiveData if you understand the basics correctly. MediatorLiveData is the suggested option. developer.android.com/reference/androidx/lifecycle/…
I also updated the answer with MediatorLiveData
Using MediatorLiveData is also one way. But it is often used when there are multiple livedata sources that you need to listen to. Because here he wants to check if livedata meals is null or empty. So we will use the Transformation LiveData extension map. The extension map is essentially generated from MediatorLiveData. In general, use MediatorLiveData in case you need to listen to many livedata sources, but if you only listen to 1 livedata source change, you should use the Transformation map extension.
Yeah, you are right, that skipped my mind. But that will be the same as my first implementation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.