Kotlin-Flow When there is no value to emit what happens - kotlin

For example we have a flow that emits 3 integer in it. Than a collector collects it from a viewModel. Finally, the collector collected 3 integer and will never collect a new value from flow because we just emitted 3 integer. What happens to the flow after that? Is it going to be cancelled or it will survive ? If it will survive does it cause memmory problems? If not should I cancel it on my own
Random Place
fun integerFLow() = flow<Int> {
emit(1)
emit(2)
emit(3)
}
ViewModel
init {
viewModelScope.launch {
integerFlow().collect { int ->
println("integer: $int")
}
}
}

Related

Kotlin flows SharedFlow not received in collectInLifeCycle inside Fragment

I am observing inside a fragment the events of a sharedflow such as this:
myEvent.collectInLifeCycle(viewLifecycleOwner) { event ->
when (state) {
//check the event. The event emited form onStart is never reached here :(
}
}
Whereas in the viewmodel I have
private val _myEvent = MutableSharedFlow<MyEvent>()
val myEvent: SharedFlow<MyEvent> = _myEvent
fun loadData() =
viewModelScope.launch {
getDataUseCase
.safePrepare(onGenericError = { _event.emit(Event.Error(null)) })
.onStart { _event.emit(Event.Loading) }
.onEach { result ->
result.onSuccess { response ->
_event.emit(Event.Something)
}
}
.launchIn(viewModelScope)
}
So the problem is that only the Event.Something is the one being properly collected from the fragment, whereas _event.emit(Event.Loading) is not being collected... If I debug it goes to the onStart, but it is never called in the fragment.
Your SharedFlow needs to have a replay so that collectors always get at least the most recent value. Otherwise, if you emit to the Flow before the collector is registered, it will never see anything emitted. Do this:
private val _myEvent = MutableSharedFlow<MyEvent>(replay = 1)
Personally, unless I'm missing some detail here that would change my mind, I would simplify all your code to avoid having to manually call loadData(). Something like this but I'm guessing a bit because I don't know all your types and functions.
val myEvent: SharedFlow<MyEvent> = flow {
emit(Event.Loading)
emitAll(
getDataUseCase
.transform { result ->
result.onSuccess { response ->
emit(Event.Something)
}
}
.catch { error -> emit(Event.Error(null)) }
)
}.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)

Kotlin - Debounce Only One Specific Value When Emitting from Flow

I have two flows that are being combined to transform the flows into a single flow. One of the flows has a backing data set that emits much faster than the the other.
Flow A - emits every 200 ms
Flow B - emits every ~1s
The problem I am trying to fix is this one:
combine(flowA, flowB) { flowAValue, flowBValue // just booleans
flowAValue && flowBValue
}.collect {
if(it) {
doSomething
}
}
Because Flow A emits extremely quickly, the boolean that's emitted can get cleared rapidly, which means that when flowB emits true, flowA already emitted true and the state is now false.
I've attempted something like:
suspend fun main() {
flowA.debounce {
if (it) {
1250L
} else {
0L
}
}.collect {
println(it)
}
}
But this doesn't work as sometimes the true values aren't emitted - inverting the conditional (so that if(true) = 0L else 1250L) also doesn't work. Basically what I'm looking for is that if flowA is true - hold that value for 1 second before changing values. Is something like that possible?
I made this use conflated on the 2nd flow, that is drastically faster, so that zipping them will always take the latest value from fastFlow, when slowFlow is finally ready, if you don't use conflated on the 2nd flow, it will always be the first time both emit.
fun forFlow() = runTest {
val slowString = listOf("first", "second", "third", "fourth")
val slowFlow = flow {
slowString.forEach {
delay(100)
emit(it)
}
}
val fastFlow = flow {
(1 until 1000).forEach { num ->
delay(5)
emit(num)
}
}.conflate()
suspend fun zip() {
slowFlow.zip(fastFlow) { first, second -> "$first: $second" }
.collect {
println(it)
}
}
runBlocking {
zip()
}
println("Done!")
}
With Conflated on fastFlow:
first: 1
second: 15
third: 32
fourth: 49
Done!
Without Conflated on fastFlow:
first: 1
second: 2
third: 3
fourth: 4
Done!

How to use callbackFlow within a flow?

I'm trying to wrap a callbackFlow within an outer flow - there are items I'd like to emit from the outer flow, but I've got an old callback interface, which I'd like to adapt to Kotlin flow. I've looked at several examples of usage of callbackFlow but I can't figure out how to properly trigger it within another flow.
Here's an example:
class Processor {
fun start(processProgress: ProcessProgressListener) {
processProgress.onFinished() //finishes as soon as it starts!
}
}
interface ProcessProgressListener {
fun onFinished()
}
//main method here:
fun startProcess(processor: Processor): Flow<String> {
val mainFlow = flow {
emit("STARTED")
emit("IN_PROGRESS")
}
return merge(processProgressFlow(processor), mainFlow)
}
fun processProgressFlow(processor: Processor) = callbackFlow {
val listener = object : ProcessProgressListener {
override fun onFinished() {
trySend("FINISHED")
}
}
processor.start(listener)
}
The Processor takes a listener, which is triggered when the process has finished. When that happens, I would like to emit the final item FINISHED.
The way I invoke the whole flow is as follows:
runBlocking {
startProcess(Processor()).collect {
print(it)
}
}
But, I get no output whatsoever. If I don't use the megre and only return the mainFlow, however, I do get the STARTED and IN_PROGRESS items though.
What am I doing wrong?
You forgot to call awaitClose in the end of callbackFlow block:
fun processProgressFlow(processor: Processor) = callbackFlow<String> {
val listener = object : ProcessProgressListener {
override fun onFinished() {
trySend("FINISHED")
channel.close()
}
}
processor.start(listener)
/*
* Suspends until 'channel.close() or cancel()' is invoked
* or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled).
* In both cases, callback will be properly unregistered.
*/
awaitClose { /* unregister listener here */ }
}
awaitClose {} should be used in the end of callbackFlow block.
Otherwise, a callback/listener may leak in case of external cancellation.
According to the callbackFlow docs:
awaitClose should be used to keep the flow running, otherwise the channel will be closed immediately when block completes. awaitClose argument is called either when a flow consumer cancels the flow collection or when a callback-based API invokes SendChannel.close manually and is typically used to cleanup the resources after the completion, e.g. unregister a callback. Using awaitClose is mandatory in order to prevent memory leaks when the flow collection is cancelled, otherwise the callback may keep running even when the flow collector is already completed. To avoid such leaks, this method throws IllegalStateException if block returns, but the channel is not closed yet.

Kotlin Flow: How to unsubscribe/stop

Update Coroutines 1.3.0-RC
Working version:
#FlowPreview
suspend fun streamTest(): Flow<String> = channelFlow {
listener.onSomeResult { result ->
if (!isClosedForSend) {
offer(result)
}
}
awaitClose {
listener.unsubscribe()
}
}
Also checkout this Medium article by Roman Elizarov: Callbacks and Kotlin Flows
Original Question
I have a Flow emitting multiple Strings:
#FlowPreview
suspend fun streamTest(): Flow<String> = flowViaChannel { channel ->
listener.onSomeResult { result ->
if (!channel.isClosedForSend) {
channel.sendBlocking(result)
}
}
}
After some time I want to unsubscribe from the stream. Currently I do the following:
viewModelScope.launch {
beaconService.streamTest().collect {
Timber.i("stream value $it")
if(it == "someString")
// Here the coroutine gets canceled, but streamTest is still executed
this.cancel()
}
}
If the coroutine gets canceled, the stream is still executed. There is just no subscriber listening to new values. How can I unsubscribe and stop the stream function?
A solution is not to cancel the flow, but the scope it's launched in.
val job = scope.launch { flow.cancellable().collect { } }
job.cancel()
NOTE: You should call cancellable() before collect if you want your collector stop when Job is canceled.
You could use the takeWhile operator on Flow.
flow.takeWhile { it != "someString" }.collect { emittedValue ->
//Do stuff until predicate is false
}
For those willing to unsubscribe from the Flow within the Coroutine scope itself, this approach worked for me :
viewModelScope.launch {
beaconService.streamTest().collect {
//Do something then
this.coroutineContext.job.cancel()
}
}
With the current version of coroutines / Flows (1.2.x) I don't now a good solution. With onCompletion you will get informed when the flow stops, but you are then outside of the streamTest function and it will be hard to stop listening of new events.
beaconService.streamTest().onCompletion {
}.collect {
...
}
With the next version of coroutines (1.3.x) it will be really easy. The function flowViaChannel is deprecated in favor for channelFlow. This function allows you to wait for closing of the flow and do something in this moment, eg. remove listener:
channelFlow<String> {
println("Subscribe to listener")
awaitClose {
println("Unsubscribe from listener")
}
}
When a flow runs in couroutin scope, you can get a job from it to controls stop subscribe.
// Make member variable if you want.
var jobForCancel : Job? = null
// Begin collecting
jobForCancel = viewModelScope.launch {
beaconService.streamTest().collect {
Timber.i("stream value $it")
if(it == "someString")
// Here the coroutine gets canceled, but streamTest is still executed
// this.cancel() // Don't
}
}
// Call whenever to canceled
jobForCancel?.cancel()
For completeness, there is a newer version of the accepted answer. Instead of explicitly using the launch coroutine builder, we can use the launchIn method directly on the flow:
val job = flow.cancellable().launchIn(scope)
job.cancel()
Based on #Ronald answer this works great for testing when you need to make your Flow emits again.
val flow = MutableStateFlow(initialValue)
flow.take(n).collectIndexed { index, _ ->
if (index == something) {
flow.value = update
}
}
//your assertions
We have to know how many emissions in total we expect n and then we can use the index to know when to update the Flow so we can receive more emissions.
If you want to cancel only the subscription being inside it, you can do it like this:
viewModelScope.launch {
testScope.collect {
return#collect cancel()
}
}
There are two ways to do this that are by design from the Kotlin team:
As #Ronald pointed out in another comment:
Option 1: takeWhile { //predicate }
Cancel collection when the predicate is false. Final value will not be collected.
flow.takeWhile { value ->
value != "finalString"
}.collect { value ->
//Do stuff, but "finalString" will never hit this
}
Option 2: transformWhile { //predicate }
When predicate is false, collect that value, then cancel
flow.transformWhile { value ->
emit(value)
value != "finalString"
}.collect { value ->
//Do stuff, but "finalString" will be the last value
}
https://github.com/Kotlin/kotlinx.coroutines/issues/2065

Observable that buffers items when no subscribers, then emits them and clears the buffer when it is subscribed to?

I want an observable that:
Can emit items on demand and never really completes (a hot observable?)
Is aware of when it has subscribers or not
If no subscribers, it will buffer items that I tell it to emit
When subscribed, it will emit the buffered items in order, then clear the buffer, and then continue to allow me to emit more items
When unsubscribed (subscriber is disposed?), it will go back to buffering.
Also:
There is only expected to be one subscriber at a time
It does not need to be thread safe
Here's sort of a pseudocode of what I am thinking -- I don't have the necessary callbacks though to do this the right way. Also it would be nice if I could wrap it all up in an Observable or Subject.
class RxEventSender {
private val publishSubject = PublishSubject.create<Action>()
val observable: Observable<Action> = publishSubject
private val bufferedActions = arrayListOf<Action>()
private var hasSubscribers = false
fun send(action: Action) {
if (hasSubscribers) {
publishSubject.onNext(action)
} else {
bufferedActions.add(action)
}
}
//Subject was subscribed to -- not a real callback
fun onSubscribed() {
hasSubscribers = true
bufferedActions.forEach {action ->
publishSubject.onNext(action)
}
bufferedActions.clear()
}
//Subject was unsubscribed -- not a real callback
fun onUnsubscribed() {
hasSubscribers = false
}
}
Use a ReplaySubject. It has unbounded and bounded versions if you get worried about the buffer getting too big.
After spending some time, I think this works well enough for my needs. It's not wrapped up nicely in a Subject or Observable but all I needed was to emit items and subscribe.
class RxEventSender<T> {
private val bufferedEvents = arrayListOf<T>()
private val publishSubject = PublishSubject.create<T>()
val observable: Observable<T> = publishSubject
.mergeWith(Observable.fromIterable(bufferedEvents))
.doOnDispose {
bufferedEvents.clear()
}
fun send(event: T) {
if (publishSubject.hasObservers()) {
publishSubject.onNext(event)
} else {
bufferedEvents.add(event)
}
}
}