question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unexpected tryEmit behaviour

See original GitHub issue

tryEmit doesn’t attempt to emit a value after first subscriber joined and returns false. Setting replay or extraBufferCapacity > 0 or replacing tryEmit by emit resolves the issue

    @Test
    fun tryEmitExample() = runBlocking {
        val sharedFlow = MutableSharedFlow<Long>()

        val asyncReceiver = async(){
            delay(300)
                sharedFlow.collect{
                    println("Received on 1 $it")
                }
            println("Done")
        }

        repeat(4) {
            delay(100)
            val res = sharedFlow.tryEmit(System.currentTimeMillis())
            println("Emitted ${System.currentTimeMillis()} Subscribers: ${sharedFlow.subscriptionCount.value} try: $res")
        }
        asyncReceiver.cancel()
    }
Emitted 1605303026489 Subscribers: 0 try: true
Emitted 1605303026592 Subscribers: 0 try: true
Emitted 1605303026693 Subscribers: 1 try: false
Emitted 1605303026799 Subscribers: 1 try: false
    @Test
    fun emitExample() = runBlocking {
        val sharedFlow = MutableSharedFlow<Long>()

        val asyncReceiver = async(){
            delay(300)
            sharedFlow.collect{
                println("Received on 1 $it")
            }
            println("Done")
        }

        repeat(4) {
            delay(100)
            sharedFlow.emit(System.currentTimeMillis())
            println("Emitted ${System.currentTimeMillis()} Subscribers: ${sharedFlow.subscriptionCount.value}")
        }
        asyncReceiver.cancel()
    }
Emitted 1605303080955 Subscribers: 0
Emitted 1605303081061 Subscribers: 0
Received on 1 1605303081162
Emitted 1605303081166 Subscribers: 1
Received on 1 1605303081267
Emitted 1605303081267 Subscribers: 1

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:17 (6 by maintainers)

github_iconTop GitHub Comments

25reactions
lukas1commented, May 29, 2021

I feel the only people who might get confused with flow default behaviour, are those coming from some other reactive library and not carefully reading docs/making assumptions. But idk about this.

I think people who are new to coroutines and Kotlin Flow in general would be confused about this as well. I myself was surprised when I encountered it first (and I’m not even new to reactive frameworks or coroutines) and so was a colleague of mine. I think this is confusing to anyone with little experience.

I personally prefer if all the arguments are zero by default, if I didn’t ask for buffer capacity, don’t give me any, seems fair. I hate the fact that in the WhileSubscribed constructor, it has non zero default

You’re describing different situation. I am not suggesting to define non-zero default. I’m suggesting to force developer to select values by themselves, to force them to think about it.

In my projects a sensible default might be MutableSharedFlow(replay = 0, extraBufferCapacity = 1, bufferOverflow = BufferOverflow.DROP_OLDEST)

This might not serve everyone, so I don’t want this to be the default, but at least to me, I NEVER want MutableSharedFlow(replay = 0, extraBufferCapacity = 0, bufferOverflow = BufferOverflow.SUSPEND). It would render tryEmit completely useless, which I don’t want probably 100% of time. Should anybody find it necessary or useful, they will still be able to do so, they’ll just specify the buffer capacity, to ensure they understand implications of their decision.

20reactions
lukas1commented, May 29, 2021

@elizarov I would like to renew discussion about this one.

First of all, the documentation about default implementation of MutableSharedFlow() is positioned above SharedFlow. To me, this is completely unexpected. The way I usually look at the documentation is, that I cmd click on the type, that I am working with and look through documentation there. So I’d click on the invocation of MutableSharedFlow and read what it has to offer. That explanation is not present there at all, so I have completely overlooked it until I found this issue (which I had been specifically searching for).

I’d at least suggest to either move that piece of documentation above the function MutableSharedFlow, or if nothing else, add a link from documentation of MutableSharedFlow to documentation of SharedFlow with the explanation, that some concepts regarding the extraBufferCapacity and default values are explained in SharedFlow’s documentation. (I’d probably vote for the former solution)

Second, the default implementation of MutableSharedFlow is unexpected to me. Probably nobody wants that implementation. I can’t imagine there are many developers, who would expect, that if they create their flow like this: val testFlow = MutableSharedFlow<String>(), it means that all attempts to tryEmit() will immediately drop the event. Even if developers would expect that, it’s rarely the case that they would want that default behavior.

So I’d suggest to remove the default parameter values at least for replay and extraBufferCapacity parameters and require developers to provide some values of their own, to match their needs. Default values make sense if a sensible default is chosen, but personally I don’t believe replay=0, extraBufferCapacity=0 is a sensible default. With my suggestions, everybody will have to think about what values to choose and that way they’ll avoid bugs in advance. (Somebody just starting to learn about coroutines API is guaranteed to use default implementation of MutableSharedFlow and expect tryEmit to work, they won’t think twice about it)

What do you think about these suggestions?

Also, what do other devs think?

Read more comments on GitHub >

github_iconTop Results From Across the Web

MutableSharedFlow is kind of complicated | by Lukas Vyletel
What tryEmit method does, in order to not block thread until an event is collected, is that it sends a value to MutableSharedFlow...
Read more >
Do or do not; there is no tryEmit() - Dan Lew Codes
On top of that, tryEmit() returns false , indicating that it knows it ... replay cache and using it will not result in...
Read more >
My MutableStateFlow Didn't Emit! - Handstand Sam
Use immutable data structures (classes with all val properties) with MutableStateFlow to avoid unexpected behavior.
Read more >
Rx to Coroutines Concepts, Part 5: Shared Flows
And if you don't have any buffer, tryEmit won't do anything except wake up active collectors who are currently suspended. It will usually...
Read more >
Reactor 3 Reference Guide
Each operator adds behavior to a Publisher and wraps the previous step's ... When using the tryEmit* API, parallel calls fail fast.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found