Reputation: 1283
I'm trying to build an application that use kotlin flows from the data layer up to the view but struggle so much with simple problems like this one.
I'm collecting integer values from a StateFlow coming from my data layer, and would like to build a list out of it before I share it to me viewmodel and view throught another StateFlow.
Sample code:
class MyDataSource {
val inputFlow: StateFlow<Int> = TODO()
}
class MyService(
dataSource: MyDataSource
) {
private val intList: MutableList<Int> = ArrayList()
private val _intFlow = MutableStateFlow<List<Int>>(listOf())
val intFlow: StateFlow<List<Int>> = _intFlow
init {
GlobalScope.launch(Dispatchers.IO) {
dataSource.inputFlow.collect {
if (!intList.contains(it))
intList.add(it)
_intFlow.emit(ArrayList(intList))
}
}
}
}
Isn't there a better way to do this? Like a flow operator I missed? It seems especially ugly since I need to build a new ArrayList every time because other wise even if the content of the List changed the flow won't send the new value as it is still the same List.
Upvotes: 1
Views: 9946
Reputation: 9944
Does scan
do what you need?
From the docs:
Folds the given flow with operation, emitting every intermediate result, including initial value. [...] For example:
flowOf(1, 2, 3).scan(emptyList<Int>()) { acc, value -> acc + value }.toList()
will produce[], [1], [1, 2], [1, 2, 3]]
.
Upvotes: 0
Reputation: 93521
There is some cleanup you can do, but I think the arrangement of flowing an ever growing list is too specific for there to be any all-in-one solution in the core library.
You should avoid using GlobalScope. This service class should more properly have its own scope so you can manage lifecycle if need be.
Your backing list should be a set since you are in effect using it as a poor-performance set by checking if the objects are in it before adding to it.
It would be cleaner to use chain operators and stateIn
rather than manipulating a MutableStateFlow and having to use a backing property.
class MyService(
dataSource: MyDataSource
) {
private val intSet = mutableSetOf<Int>()
private val scope = CoroutineScope(Dispatchers.Default)
val intFlow = dataSource.inputFlow
.map {
intSet += it
intSet.toList()
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
}
Edit: Here's an untested attempt at avoiding set-to-list copies and new allocations on each iteration:
class MyService(
dataSource: MyDataSource
) {
private val intSet = mutableSetOf<Int>()
private var currentList = mutableListOf<Int>()
private var previousList = mutableListOf<Int>()
private var lastEmittedValue: Int? = null
private val scope = CoroutineScope(Dispatchers.Default)
val intFlow = dataSource.inputFlow
.mapNotNull { newValue ->
if (!intSet.add(newValue))
return@mapNotNull null
lastEmittedValue?.let {
previousList += it
} // both backing lists now identical
lastEmittedValue = newValue
previousList = currentList.also { currentList = previousList }
// previousList is the one missing lastEmittedValue on next iteration:
currentList += newValue
currentList
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
}
Upvotes: 1