Why isn't there a coroutine Flow livedata builder ...
# android
r
Why isn't there a coroutine Flow livedata builder which returns mutableLivedata?
This would be the 'old' patern:
Copy code
class MyViewModel : ViewModel() {

private val _items = MutableLiveData<List<Item>>()

val items: LiveData<List<Item>>
        get() = _items

init {
        viewModelScope.launch {
            _items.value = fetchDataFromNetwork()
        }

}
With the new coroutines Flow livedata builder you can reduce this to:
Copy code
val items: LiveData<List<Item>> = liveData {
     emit(fetchDataFromNetwork())
}
But what if I want to add logic inside my viewmodel that alters the value inside the 'items' value?
v
I believe you need to use MediatorLiveData for that.
👍 1
r
Can you provide a code snippet that demonstrates this?
f
Just for Clarity: What is it you're trying to do? do you want to change the livedata value from viewmodel? If yes try changing the source of flow using
emitSource
this acts as a
MediatorLiveData.addSource
as Vishal said. Then something like this should work:
Copy code
fun getItems(changeSource: () -> LiveData<List<Item>>) = 
	liveData { 
		
		emit(fetchDatafromNetwork())
		
		//change source
		emitSource(changeSource())
	}
But then this changing of source will be invoked from within livedata scope. So, if you want to change it from outside and want full control you can extend MediatorLiveData and run your coroutine scope inside it.
and the last sentence is the reason why you don't have builder which returns mutablelivedata, because you can always build one using
MediatorLiveData
which is also used in building the current flow livedata
r
Below my full code. I have logic for animations. I am fetching tags from a network API. I then alter the result inside the viewModel.
Copy code
class OnboardingGetToKnowViewModel : ViewModel() {

    private val _onboardingTags = MutableLiveData<List<OnboardingTag>>()

    val onboardingTags: LiveData<List<OnboardingTag>>
        get() = _onboardingTags

    private var _selectedTags = MutableLiveData<List<OnboardingTag>>()

    val selectedTags: LiveData<List<OnboardingTag>>
        get() = _selectedTags

    fun addTag(tag: OnboardingTag) {
        val existingList = _selectedTags.value ?: emptyList()
        val newList = existingList + listOf(tag)
        _selectedTags.value = newList

        //for grid animation purpose change photo index so 1 becomes 3, 3 becomes 5 etc.
        val index = _onboardingTags.value!!.indexOf(tag)
        val list = _onboardingTags.value!!.toMutableList()
        for (i in index until _onboardingTags.value!!.size step 2) {
            val nextItem = list.getOrNull(i + 2)
            if (nextItem != null) {
                list[i] = nextItem
            } else {
                list.removeAt(i)
            }
        }
        _onboardingTags.value = list
    }

    fun removeTag(tag: OnboardingTag) {
        val existingList = _selectedTags.value ?: emptyList()
        val newList = existingList - listOf(tag)
        _selectedTags.value = newList
        //for grid animation purpose change photo index so it gets added at the top
        val oldPhotos = _onboardingTags.value!!
        val index = if (oldPhotos.size % 2 == 0) 2 else 3
        val list = oldPhotos.toMutableList()
        for (i in index until oldPhotos.size step 2) {
            list[i] = oldPhotos[i - 2]
        }
        if (oldPhotos.size > 1) {
            list.add(oldPhotos[oldPhotos.size - 2])
            list[index - 2] = tag
        } else {
            list.add(tag)
        }
        _onboardingTags.value = list
    }

    val lastItemTagged = Transformations.map(selectedTags) { list ->
        list.lastOrNull()
    }

    val secondLastItemTagged = Transformations.map(selectedTags) { list ->
        list.getOrNull(list.lastIndex - 1)
    }

    val extraItems = Transformations.map(selectedTags) { list ->
        list.size - 2
    }

    init {
        viewModelScope.launch {
            _onboardingTags.value = Services.followApi.fetchOnboardingTags()
        }
    }
}
z
Seems like your code has a race - what if someone calls addTag before the service call returns, and then the call returns in middle of addTag executing, in between some of the accesses of _onboardingTags? Or if the call returns after addTag updates the value of _onboardingTags and overwrites it?
To your original question, that would mean the LiveData would have two+ sources of truth, which can make code tricky to reason about and result in races like this, for example.
1