Home > Net >  How to pause emitting Flow in Kotlin?
How to pause emitting Flow in Kotlin?

Time:03-15

Suppose I have some data that I need to transfer to the UI, and the data should be emitted with a certain delay, so I have a Flow in my ViewModel:

val myFlow = flow {
    listOfSomeData.forEachIndexed { index, data -> 
        //....
        emit(data.UIdata)
        delay(data.requiredDelay)
    }
}

Somewhere in the UI flow is collected and displayed:

@Composable
fun MyUI(viewModel: ViewModel) {
    val data by viewModel.myFlow.collectAsState(INITIAL_DATA)
    //....
}

Now I want the user to be able to pause/resume emission by pressing some button. How can i do this?

The only thing I could come up with is an infinite loop inside Flow builder:

val pause = mutableStateOf(false)
//....
val myFlow = flow {
    listOfSomeData.forEachIndexed { index, data -> 
        emit(data.UIdata)
        delay(data.requiredDelay)
        while (pause.value) { delay(100) }  //looks ugly
    }
}

Is there any other more appropriate way?

CodePudding user response:

If you don't need a specific delay you can use flow.filter{pause.value != true}

CodePudding user response:

You can tidy up your approach by using a flow to hold pause value then collect it:

val pause = MutableStateFlow(false)
//....
val myFlow = flow {
    listOfSomeData.forEachIndexed { index, data -> 
        emit(data.UIdata)
        delay(data.requiredDelay)
        if (pause.value) pause.first { isPaused -> !isPaused } // suspends
    }
}

Do you need mutableStateOf for compose? Maybe you can transform it into a flow but I'm not aware how it looks bc I don't use compose.

A bit of a creative rant below:

I actually was wondering about this and looking for more flexible approach - ideally source flow should suspend during emit. I noticed that it can be done when using buffered flow with BufferOverflow.SUSPEND so I started fiddling with it.

I came up with something like this that lets me suspend any producer:

// assume source flow can't be accessed
val sourceFlow = flow {
    listOfSomeData.forEachIndexed { index, data -> 
        emit(data.UIdata)
        delay(data.requiredDelay)
    }
}

val pause = MutableStateFlow(false)
val myFlow = sourceFlow
    .buffer(Channel.RENDEZVOUS, BufferOverflow.SUSPEND)
    .transform {
        if (pause.value) pause.first { isPaused -> !isPaused }
        emit(it)
    }
    .buffer()

It does seem like a small hack to me and there's a downside that source flow will still get to the next emit call after pausing so: n value gets suspended inside transform but source gets suspended on n 1.

If anyone has better idea on how to suspend source flow "immediately" I'd be happy to hear it.

  • Related