
Introduction
SharedFlow is a hot flow. Actually, a Flow is a stream of data that can be computed asynchronously. There are two types of Flows,
- Cold Flow – Emits only when a collector is present
- Hot Flow -Emits even though there is no collector
Since SharedFlow is a hot flow, it can emit regardless of the collector’s presence. And it doesn’t hold any value like StateFlow unless specified.
For example, see the code below
val sharedFlow = MutableSharedFlow<String>()
This creates a MutableSharedFlow of type String. But inside constructor, three parameters replay, extraBufferCapacity and onBufferOverflow are not specified. But still kotlin compiler doesn’t throw any error as all the 3 parameters have default values as shown in the internal implementation of MutableSharedFlow.
Suppress("FunctionName", "UNCHECKED_CAST") public fun <T> MutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND ): MutableSharedFlow<T>
But in StateFlow, we need an initial value like
val stateFlow = MutableStateFlow(0)
Okay, since we are going to focus more on SharedFlow, let’s deep dive with practical examples.
Practical Examples
val sharedFlow = MutableSharedFlow<String>() lifecycleScope.launch { sharedFlow.emit("Ram") sharedFlow.emit("Kumar") // Emission sharedFlow .onEach { println(it) } // Collection .launchIn(this) }
In the above code, the first line of code is a MutableSharedFlow creation that we already discussed.
Then we are launching a coroutine on lifecycleScope which is either an activity or a fragment. So once we use lifecycleScope, it’s dispatcher is Dispatchers.Main.immediate which executes the code immediately.
Inside this coroutine, we are emitting 2 values of SharedFlow and using onEach to iterate through all the emitted values and print them. And finally, we are using launchIn(this).
Internal implementation of launchIn
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch { collect() // tail-call }
As you can see, it internally calls collect(), which is a terminal operator. So similar to collect, we can also use launchIn as a collector to collect emitted values of SharedFlow. It launches a collector in a separate non-blocking coroutine.
But if we run the above code, we may expect that Ram and Kumar will be printed but nothing will get printed. Why?
Because SharedFlow doesn’t remember past emissions.
That means we have emitted first (past emission) and collected next. So it doesn’t print anything.
Okay, let’s collect first and then emit
val sharedFlow = MutableSharedFlow<String>() lifecycleScope.launch { sharedFlow .onEach { println(it) } // Collection .launchIn(this) sharedFlow.emit("Ram") // Emission sharedFlow.emit("Kumar") }
Now, it won’t print anything. Why?
- Even though
launchIn(this)
starts the collection, it’s asynchronous — the collector may not yet be active whenemit()
is called.
So what’s happening:
onEach().launchIn(this)
launches a collector in a separate coroutine.
The next lines (emit(...)
) run immediately, possibly before the collector is ready.
Result: emissions are lost → nothing is printed.
To solve this, we have to emit in a separate coroutine like below.
val sharedFlow = MutableSharedFlow<String>() lifecycleScope.launch { sharedFlow .onEach { println(it) } // Collection .launchIn(this) launch { sharedFlow.emit("Ram") // Emission sharedFlow.emit("Kumar") } }
Or
Add a delay. Since delay() is a suspend function, it delays the emission process until the collector becomes active or ready.
val sharedFlow = MutableSharedFlow<String>() lifecycleScope.launch { sharedFlow .onEach { println(it) } // Collection .launchIn(this) delay(100) sharedFlow.emit("Ram") // Emission sharedFlow.emit("Kumar") }
Now the output will be as expected.
Ram Kumar
The same is the case when we use collect instead of launchIn(this), like below.
val sharedFlow = MutableSharedFlow<String>() lifecycleScope.launch { sharedFlow.collect { println(it) } // Collection sharedFlow.emit("Ram") // Emission sharedFlow.emit("Kumar") }
In the above code, the Kotlin compiler will issue a warning Unreachable code because
collect is suspending, so any code after it in the same coroutine will not execute until the flow completes.
Now let’s emit first (past emission) and collect by using replay in SharedFlow.
val sharedFlow = MutableSharedFlow<String>(replay = 1) lifecycleScope.launch { sharedFlow.emit("Ram") sharedFlow.emit("Kumar") // Emission sharedFlow .onEach { println(it) } // Collection .launchIn(this) }
In the above, we have specified a replay value of 1 while initializing MutableSharedFlow. By this, we want
- SharedFlow to remember the last emission if replay = 1.
- SharedFlow to remember the last 2 emissions if replay = 2 and so on.
Also, we have emitted and collected inside the same coroutine. When we run the above code, the output will be
Kumar
Since we have asked SharedFlow to remember the most recent emission, only that value is printed.
Job Offers
Conclusion
Key points to remember on SharedFlow:
- Always collect and then start emitting if no replay value is set
- Set a replay value while initializing SharedFlow when emission is done first, and then collection is started.
Thanks for reading this article. If you like this post, Please give a clap (👏).
Also, if you like to support me through
https://buymeacoffee.com/dilipchandar, please do.
Let’s connect on LinkedIn https://www.linkedin.com/in/dilip-chandar-97570158?
This article was previously published on proandroiddev.com.