Cold & Hot Streams in Kotlin Flow

In this lesson, you’ll learn about cold and hot streams in Kotlin Flow. Understanding the distinction between these two types of streams is important for effectively managing data flows in your apps.

Cold Streams

Cold streams are streams that don’t start emitting items until an observer subscribes. They’re passive and only begin their operations when a consumer is present. This behavior ensures that all subscribers see the entire sequence from the beginning, making cold streams ideal for precise and repeatable operations.

Suppose, in your carrot factory, you always want to process carrots in a specific order based on carrot type:

fun orderOfProcessing(): Flow<String> = flow {
  emit("Nantes")
  delay(1000)
  emit("Imperator")
  delay(1000)
  emit("Chantenay")
  delay(1000)
}

fun processCarrots(scope: CoroutineScope = GlobalScope) {
  val firstProcessingLine = scope.launch {
    orderOfProcessing().collect { type ->
      println("Processing carrots on Line 1: $type")
    }
  }

  val secondProcessingLine = scope.launch {
    orderOfProcessing().collect { type ->
      println("Processing carrots on Line 2: $type")
    }
  }

  // ...
}

fun main() = runBlocking {
  processCarrots()
  delay(5000)
}

In this example, orderOfProcessing() generates a sequence of strings per second, each representing a carrot type. This flow is cold because each collect call starts the sequence from the beginning, ensuring that each collection is independent.

Hot Streams

Hot streams, on the other hand, are active regardless of the presence of subscribers. They emit items, and these items can be lost if no observers are collecting at the time of emission. This makes hot streams ideal for representing events or data that are independent of individual subscribers’ timing, such as UI events or sensor data.

Suppose you want to switch what type of carrots you’re processing on your lines, and the second processing line has a delay of one second:

val processingType: MutableSharedFlow<String> = MutableSharedFlow<String>()

suspend fun switchCarrotType(type: String) {
  processingType.emit(type)
}

fun processCarrots(scope: CoroutineScope = GlobalScope) {
  val firstProcessingLine = scope.launch {
    processingType.collect { type ->
      println("Processing carrots on Line 1: $type")
    }
  }

  val secondProcessingLine = scope.launch {
    delay(1000)
    processingType.collect { type ->
      println("Processing carrots on Line 2: $type")
    }
  }

  // ...
}

fun main() = runBlocking {
  processCarrots()
  switchCarrotType("Nantes")
  delay(2000)
  switchCarrotType("Imperator")
  delay(5000)
}

In this example, processingType is a MutableSharedFlow. A SharedFlow is a hot flow that shares values you emit to it to all its collectors. switchCarrotType() emits new types of carrots to be processed, simulating a dynamic event source. This hot stream starts emitting immediately, regardless of whether any processing lines are collecting the data.

As a result, because the second line starts collecting after one second, it’ll miss the first emission, “Nantes.”

Wrap-Up

In this lesson, you’ve explored the differences between cold and hot streams in Kotlin Flow. Cold streams replay the emitted data for each collector, ensuring consistency, while hot streams emit data independently of subscribers, suitable for real-time and event-based data.

In the next lesson, you’ll see practical applications of cold and hot streams to help you better integrate them into your apps.

See forum comments
Download course materials from Github
Previous: Introduction Next: Hot Streams Demo