Goal
I have a computation heavy function calculateItems: suspend (String) -> Sequence<String>
.
I want to load the outputs of this calculation while showing the progress to the user and keeping the UI responsive. I also want to cancel this calculation in case the user input changes, and start again once the user clicks a button.
Approach
I use produceState
. Inside produceState
, I delegate this computation to a non-Main-dispatcher in order to keep the UI responsive. I also collect the items emitted from that sequence and update the progress on each received item.
val totalSize = 255 // for sake of this example
var input by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue("Test Input"))
}
var doCalculation by rememberSaveable(input) {
mutableStateOf(false)
}
val resultState by produceState(
State.Null as State,
input,
doCalculation,
) {
value = DealingState.Null
if (!doCalculation) {
value = DealingState.Null
return@produceState
}
value = DealingState.Loading(
progress = 0,
required = totalSize,
)
launch(Dispatchers.Default) {
runCatching {
val resultList = mutableListOf<String>()
calculateItems(input).forEach {
resultList.add(it)
value = State.Loading(
progress = resultList.size,
required = totalSize,
)
}
value = State.Success(resultList)
}.getOrElse {
value = State.Failure(it)
}
}
}
What I Tried
I tried the following things:
scope.cancel()
when a new state is to be produced.val scope = CoroutineScope(Dispatchers.Default)
val resultState by produceState(...) {
scope.cancel()
....
scope.launch(Dispatchers.Default) {...}
}
launch
and cancel the job or the childrenvar job: Job? = null
val resultState by produceState(...) {
job?.cancel() // and job?.cancelChildren()
....
job = launch(Dispatchers.Default) {...}
}
Try making your inner coroutine cancellable by adding ensureActive()
, for example:
calculateItems(input).forEach {
ensureActive()
resultList.add(it)