Home > Enterprise >  Wait for first Flow emit and update second Flow whenever there is a change on first one
Wait for first Flow emit and update second Flow whenever there is a change on first one

Time:07-24

I implemented an API call and I would like to allow the user to choose the sort order, keeping the choice saved in Datastore Preferences.
But I couldn't implement this idea, since both are returning a Flow, I don't know how to make one Flow wait for the other, and whenever the first one emits a new value, update the second.

Some example code I'm trying:

interface PreferencesRepository {
    fun getFilter(): Flow<OrderType>
}

class PreferencesRepositoryImpl @Inject constructor() : PreferencesRepository {
    override fun getFilter(): Flow<OrderType> = flow {
        delay(timeMillis = 300) // simulate initial read delay from datastore
        emit(value = OrderType.ASCENDING)
        delay(timeMillis = 3000) // simulate a order change after 3 secs
        emit(value = OrderType.DESCENDING)
    }
}
interface ItemsRepository {
    fun getItems(order: OrderType): Flow<List<ItemModel>>
}

class ItemsRepositoryImpl @Inject constructor() : ItemsRepository {
    private val dummyItems: List<ItemModel> = listOf(
        ItemModel(id = 1, title = "first item"),
        ItemModel(id = 2, title = "second item"),
        ItemModel(id = 3, title = "third item")
    )

    override fun getItems(order: OrderType): Flow<List<ItemModel>> = flow {
        delay(timeMillis = 1000) // simulate network call delay
        when (order) {
            OrderType.ASCENDING -> {
                emit(value = dummyItems)
            }
            OrderType.DESCENDING -> {
                emit(value = dummyItems.sortedByDescending { it.id })
            }
        }
    }
}
@HiltViewModel
class HomeViewModel @Inject constructor(
    private val itemsRepository: ItemsRepository,
    private val preferencesRepository: PreferencesRepository
) : ViewModel() {
    private val _filter = MutableLiveData<OrderType>()
    val filter: LiveData<OrderType> get() = _filter

    private val _items = MutableLiveData<List<ItemModel>>()
    val items: LiveData<List<ItemModel>> get() = _items

    init {
        // i imagine that these two methods must be unified somehow to work
        getFilter()
        getItems()
    }

    private fun getFilter() = viewModelScope.launch {
        preferencesRepository.getFilter().collectLatest { orderType ->
            _filter.value = orderType
            println(orderType.name)
        }
    }

    private fun getItems() = viewModelScope.launch {
        itemsRepository.getItems(
            order = _filter.value ?: OrderType.ASCENDING // not updated as expected
        ).collectLatest { items ->
            _items.value = items
            items.forEach { println(it.title) }
        }
    }
}

Logcat of current code:

enter image description here

CodePudding user response:

You can use either flatMapLatest() or alternatively flatMapConcat() operator:

preferencesRepository.getFilter()
    .flatMapLatest { itemsRepository.getItems(it) }

Whenever there is a new order emitted, it invokes getItems() with this order and then emits items from getItems().

flatMapLatest() only cares about the last order emitted. If new order is emitted before items for the last one are acquired, it just ignores the previous order(s) and cancels fetching of items for it (or don't even start fetching). It seems like this is what you need.

flatMapConcat() always invokes getItems() for each new order and waits for items before processing the next ordering.

Also, if this is your real case and not a simplified example for StackOverflow, then I suggest to not re-fetch items when ordering changes. You can re-order locally.

  • Related