Exploring Different Ways to Collect Kotlin Flow
Tags: kotlin, android, jetpackcompose
Published: 2023-01-13 23:00:00

**Simple app to demonstrate Kotlin flow(), emit(), collectAsState(), collect(), viewModelScope.launch(), launchWhenStarted() and repeatOnLifecycle()**

This is part of the asynchronous flow series:

* [Part 1 - Exploring Android LiveData Usages and Behaviors](https://vtsen.hashnode.dev/exploring-android-livedata-usages-and-behaviors)
    
* [Part 2 - Introduction to Kotlin Flows and Channels](https://vtsen.hashnode.dev/introduction-to-kotlin-flows-and-channels)
    
* **Part 3 - Exploring Different Ways to Collect Kotlin Flow**
    
* [Part 4 - Convert Flow to SharedFlow and StateFlow](https://vtsen.hashnode.dev/convert-flow-to-sharedflow-and-stateflow)

* [Part 5 - Flow, SharedFlow, StateFlow Class Diagram](https://vtsen.hashnode.dev/flow-sharedflow-stateflow-class-diagram)

- [Part 6 - Kotlin Flow - Combine, Merge and Zip](https://vtsen.hashnode.dev/kotlin-flow-combine-merge-and-zip)    
   
## Basic Kotlin Flow Usages

You first need to create a flow before you can collect it.

### Create `Flow<T>` and Emit `T` Value

#### 1\. Create `Flow<T>` Using `kotlinx.coroutines.flow.flow()`

The first parameter of the `flow()` is the [function literal with receiver](https://vtsen.hashnode.dev/understand-kotlin-function-literal-with-receiver-by-example). The receiver is the implementation of `FlowCollector<T>` interface, which you can call `FlowCollector<T>.emit()` to emit the `T` value.

#### 2\. Emit `T` Value Using `FlowCollector<T>.emit()`

In this code example, the `T` is an `Int`. This flow emits `Int` value from 0 → 10,000 with a 1-second interval delay.

```kotlin
class FlowViewModel: ViewModel() 
{ 
    val flow: Flow<Int> = flow { 
        repeat(10000) { value -> 
            delay(1000) 
            emit(value) 
        } 
    } 
}
```

> The `delay(1000)` simulates the process of getting the value that you want to emit (from a network call for example)

Once we have the flow, you can now collect the flow. There are different ways of collecting flow:

* `Flow.collectAsState()`
    
* `Flow.collect()`
    

### Collect Flow - `Flow.collectAsState()`

`collectAsState()` uses the [`poducestate()` compose side effect](https://vtsen.hashnode.dev/side-effects-summary-in-jetpack-compose#heading-producestate) to collect the flow and automatically convert the collected value to `State<T>` for you.

```kotlin
@Composable
fun FlowScreen() {
   val viewModel: FlowViewModel = viewModel()

   val flowCollectAsState =
       viewModel.flow.collectAsState(initial = null)
}
```

### Collect Flow - `Flow.collect()`

To collect flow, you need to call `Flow<T>.collect()` with `FlowCollector<T>` implementation. The function definition looks like this:

```kotlin
public suspend fun collect(collector: FlowCollector<T>)
```

The first parameter of the `Flow<T>.collect()` is the `FlowCollector<T>` interface, which only has one `emit()` function.

```kotlin
public fun interface FlowCollector<in T> {    
	public suspend fun emit(value: T)  
}
```

It means you need to implement the `FlowCollector<T>` interface, pass in the instance of the implementation as the first parameter to the `Flow<T>.collect()` function.

However, if you look at the `collect()` usage below, it doesn't look like the pass-in parameter is the implementation of `FlowCollector<T>` interface?

```kotlin
flow.collect { value -> _state.value = value }
```

This is a short-cut way of implementing `FlowCollector<T>` interface using [SAM conversion](https://vtsen.hashnode.dev/implement-kotlin-interfaces-with-sam-conversions). Because `FlowCollector<T>` is **functional interfacing**, using SAM conversion is allowed. The pass-in lambda is the override function of the `FlowCollector<T>.emit()` function interface.

When `Flow<T>.emit()` is called in the flow that we created earlier, this line `_state.value = value` is executed. `value` is the parameter that you pass into the `emit()` function during the flow production in `FlowViewModel` above.

Since `Flow.collect()` is a suspend function, you need to launch a coroutine to call it. These are three ways you can launch a coroutine:

* `ViewModel.viewModelScope.launch()`
    
* `LifeCycleCoroutineScope.launchWhenStarted()`
    
* `LifeCycleCoroutineScope.launch()` with `LifeCycle.repeatOnLifeCycle()`
    

#### 1\. Collect Flow Using `ViewModel.viewModelScope.launch{}`

```kotlin
class FlowViewModel: ViewModel() {  
    private val _state: MutableState<Int?> = mutableStateOf(null)  
        val state: State<Int?> = _state  
      
    fun viewModelScopeCollectFlow() {   
        viewModelScope.launch {  
            flow.collect {  value ->
                _state.value = value  
            } 
        }
    }
}
```

#### 2\. Collect Flow Using `LifeCycleCoroutineScope.LaunchWhenStarted()`

```kotlin
class FlowViewModel: ViewModel() {
    private val _state: MutableState<Int?> = mutableStateOf(null)
    val state: State<Int?> = _state

    fun launchWhenStartedCollectFlow(lifeCycleScope: LifecycleCoroutineScope) {
        lifeCycleScope.launchWhenStarted {
            flow.collect { value ->
                _state.value = value
            }
        }   
    }
}
```

#### 3\. Collect Flow Using `LifeCycle.RepeatOnLifecycle()`

```kotlin
class FlowViewModel: ViewModel() {
    private val _state: MutableState<Int?> = mutableStateOf(null)
    val state: State<Int?> = _state

    fun repeatOnCycleStartedCollectFlow(
        lifeCycleScope: LifecycleCoroutineScope,
        lifeCycle: Lifecycle) {
        
        lifeCycleScope.launch {
            lifeCycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                flow.collect { value ->
                    _state.value = value
                }
            }
        }
    }
}
```

Similarly, you can also call `Flow.collectAsStateWithLifecycle()` composable function to achieve the same result as `LifeCycle.RepeatOnLifecycle()`. The code is much shorter and you must call it within a composable function.

```kotlin
@Composable
fun FlowScreen() {
    /*...*/
    val flowCollectAsStateWithLifeCycle = 
        viewModel.flow.collectAsStateWithLifecycle(initialValue = null)
    /*...*/
}
```

## Investigate Various Collect Flow Implementations

Let's assume you have already started collecting flow, this is the summary of what happens in different lifecycle events.

| Collect Flow Implementations | ON\_STOP / Background (not visible) | ON\_START / Background (visible) | ON\_DESTROY (Configuration Changed) | ON\_DESTROY (Lifecycle Death) |
| --- | --- | --- | --- | --- |
| `collectAsState()` | Keeps emitting | Keeps emitting | Cancels emitting | Cancels emitting |
| `viewModelScope.launch()` | Keeps emitting | Keeps emitting | Keeps emitting | Cancels emitting |
| `launchWhenStarted()` | Suspends emitting | Resumes emitting | Cancels emitting | Cancels emitting |
| `repeatOnLifecycle(Lifecycle.State.STARTED)` | Cancel emitting | Restarts Emitting | Cancels emitting | Cancels emitting |

So if you care about saving resources(i.e. not emitting anything when the app is in background (not visible), you can either use `launchWenStarted()` or `repeatOnLifecycle(Lifecycle.State.STARTED)`. If you want to suspend and resume flow emission, you use `launchWenStarted()`. If you want to restart the flow emission all over again, you use `repeatOnLifecycle(Lifecycle.State.STARTED)`

On the other hand, if you don't care about wasting resources, you can use either `collectAsState()` or `viewModelScope.launch()`. If you want the flow to keep emitting even after the configuration changed (e.g. screen rotation), you use `viewModelScope.launch()`.

I think it is better to show the flow chart instead.

![Exploring_Asynchronous_Kotlin_Flow_Usages_and_Behaviors_Flow_Chart.drawio.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663836078591/i14P5dkjd.png)

> What I don't understand is this article [here](https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda) saying the `launchWhenStarted()` is not safe to collect because it keeps emitting in the background when the UI is not visible.
> 
> However, I don't see this behavior based on the experiment that I have done. It suspends the flow emission and resumes it when the UI is visible again.

To see more details on `launchWhenStarted()` and `repeatOnLifeCycle()`, refer to this article:

- [launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()](https://vtsen.hashnode.dev/launchwhencreated-vs-launchwhenstarted-vs-launchwhenresumed-vs-repeatonlifecycle) 

## Conclusion

I have been using `viewModelScope.launch()` to collect flow. If the flow doesn't constantly emit value, allowing the flow to emit in the background doesn't waste any resources, in my opinion. Also, I don't need to care about canceling, restarting or resuming the flow emission.

On the other hand, if the flow is constantly emitting values, you may want to consider using either `launchWhenStarted()` or `repeatOnLifecycle(Lifecycle.State.STARTED)`.

## Source Code

GitHub Repository: [Demo\_AsyncFlow](https://github.com/vinchamp77/Demo_AsyncFlow) (see the `FlowActivity`)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/exploring-different-ways-to-collect-kotlin-flow)*.