Skip to content

Flow: Terminal operators

Devrath edited this page Jan 19, 2024 · 20 revisions
  • Terminal operators are the operators that start the flow by connecting the flow builder, operators with the collector.
  • No flow is started/executed unless a terminal operator is used on a flow.

First

Observation

  • You can notice that we have used first operator.
  • Also, even the second edition has not happened, so emission stops after the first emission.
  • The flow is canceled once the first emission is received.
  • If there are no elements then it would throw java.util.NoSuchElementException: To prevent this we can use firstOrNull instead of just first

Code

@HiltViewModel
class TerminalOperatorsVm @Inject constructor(
    @ApplicationContext private val context: Context,
) : ViewModel() {
    companion object {
        const val emissionDelay : Long = 100
    }

    private val terminalOperatorDemo = flow {
        delay(emissionDelay)
        println("Emitting first value")
        emit(1)
        delay(emissionDelay)
        println("Emitting second value")
        emit(2)
    }

    /** *********************** DEMO's *********************** **/
    /**
     * Terminal Operator: First
     */
    fun demoFirst() {
        viewModelScope.launch {
            val result = terminalOperatorDemo.first()
            println("Result:-> $result")
        }
    }

    /** *********************** DEMO's *********************** **/
}

Output

Emitting first value
Result:-> 1

Last

Observation

  • Here also observe that all the emissions are done from the flow but only the last emission is received in flow

Code

@HiltViewModel
class TerminalOperatorsVm @Inject constructor(
    @ApplicationContext private val context: Context,
) : ViewModel() {
    companion object {
        const val emissionDelay : Long = 100
    }

    private val terminalOperatorDemo = flow <Int>{
        delay(emissionDelay)
        println("Emitting first value")
        emit(1)
        delay(emissionDelay)
        println("Emitting second value")
        emit(2)
    }

    /** *********************** DEMO's *********************** **/

    fun demoLast() {
        viewModelScope.launch {
            val result = terminalOperatorDemo.lastOrNull()
            println("Result:-> $result")
        }
    }

    /** *********************** DEMO's *********************** **/
}

Output

Emitting first value
Emitting second value
Result:-> 2

Single

Observation

  • If there is more than one element in the flow
    • If we use single it will return IllegalArgumentException: More than one element
    • If we use singleOrNull it will return null
  • If there is just one element in the flow, It will return that element.

Code

viewModelScope.launch {
    val result = terminalOperatorDemo.singleOrNull()
    println("Result:-> $result")
}

ToList And ToSet

Observation

  • It provides us the ability to convert the flow into List and Set.

Code

 fun toListAndToSet() {
        viewModelScope.launch {
            val resultList = terminalOperatorDemo.toList()
            val resultSet = terminalOperatorDemo.toSet()
            println("Result List:-> $resultList")
            println("Result Set:-> $resultSet")
        }
}

Output

Result List:-> [1, 2]
Result Set:-> [1, 2]

LaunchIn

Observation

  • LaunchIn is not a suspended function but a regular function.
  • So it will not suspend the coroutine which it is called.
  • Thus in the output observe that emissions are sent and receiving also happen before either of the co-routines to complete.

Code

@HiltViewModel
class TerminalOperatorsVm @Inject constructor(
    @ApplicationContext private val context: Context,
) : ViewModel() {
    companion object {
        const val emissionDelay : Long = 100
    }

    private val terminalOperatorDemo = flow <Int>{
        delay(emissionDelay)
        println("Emitting first value")
        emit(1)
        delay(emissionDelay)
        println("Emitting second value")
        emit(2)
    }

   
    fun launchIn() {

        val scope = CoroutineScope(EmptyCoroutineContext)

        viewModelScope.launch {
            terminalOperatorDemo
                .onEach { println("Result Collect <1>:-> $it") }
                .launchIn(scope)

            terminalOperatorDemo
                .onEach { println("Result Collect <2>:-> $it") }
                .launchIn(scope)   
        }

    }
}

Output

Emitting first value
Result Collect <1>:-> 1
Emitting first value
Result Collect <2>:-> 1
Emitting second value
Emitting second value
Result Collect <1>:-> 2
Result Collect <2>:-> 2

Differences between Launch and LaunchIn

  • If we use scope.launch{} Until the one flow inside the scope is completed the next flow is suspended and not executed, thus sequential.
  • In launchIn(scope) The coroutines are executed in parallel.
// <------------> Launch <-------------->
fun launch() {

        val scope = CoroutineScope(EmptyCoroutineContext)

        scope.launch {

            // Collect -> Starting first collection
            terminalOperatorDemo.collect{
                println("Result Collect <1>:-> $it")
            }

            // <--- Until first collection is complete, collection is suspended --->

            // Collect -> Starting second collection
            terminalOperatorDemo.collect{
                println("Result Collect <2>:-> $it")
            }

}

// <------------> LaunchIn <------------>
fun launchIn() {

        val scope = CoroutineScope(EmptyCoroutineContext)

        viewModelScope.launch {
            terminalOperatorDemo
                .onEach { println("Result Collect <1>:-> $it") }
                .launchIn(scope)

            terminalOperatorDemo
                .onEach { println("Result Collect <2>:-> $it") }
                .launchIn(scope)
        }

}
Clone this wiki locally