Blog Infos
Author
Published
Topics
, , , ,
Published
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 replayextraBufferCapacity 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 when emit() 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

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Meta-programming with K2 compiler plugins

Let’s see what’s possible with plugins using the new K2 compiler, FIR. This live demo session will go through possible use cases that reduce boilerplate code and make your code safer.
Watch Video

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Develo ...
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Jobs

Conclusion

Key points to remember on SharedFlow:

  1. Always collect and then start emitting if no replay value is set
  2. Set a replay value while initializing SharedFlow when emission is done first, and then collection is started.

This article was previously published on proandroiddev.com.

Menu