Skip to content

Flow: Terminal operators

Devrath edited this page Dec 25, 2023 · 20 revisions
Terminal Operators
First
Last
Single
ToList And ToSet
LaunchIn
Differences between Launch and LaunchIn

Terminal operators are the operators that actually start the flow by connecting the flow builder, operators with the collector.

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