Home Android & Kotlin Books Kotlin Coroutines by Tutorials

12
SharedFlow & StateFlow Written by Filip Babić

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Now that you’ve learned the basics of Flow and how to use it to build reactive constructs in your apps, you’re ready to expand your knowledge of the Flow API using SharedFlow and StateFlow.

These two types of Flow cater to two common use cases:

  • Implementing a broadcast mechanism, which takes any data sent to a Flow and shares it with all collectors simultaneously.
  • Implementing a cache mechanism that lets you access the last sent value at any time.

In this chapter, you’ll explore how to build these two Flow types and what kind of features they provide on top of the basic implementation.

Getting Started

To begin the project for this chapter, open the starter project using IntelliJ. Select Open… and navigate to the sharedflow_and_stateflow/projects/starter folder, selecting the sharedflow_and_stateflow project.

Once the project loads, find Main.kt and open it. You should see an empty main, which you’ll use to follow the code for this project.

Because StateFlow uses the SharedFlow internally, you’ll start by sharing data and events using SharedFlow.

Sharing a Flow

Sharing data with layers and services is quite common in large applications. Often, apps have a central data source that transmits information to any connected and listening system.

val sharedFlow = MutableSharedFlow<Int>(replay = 2)
public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>
fun main() {
  val sharedFlow = MutableSharedFlow<Int>(2)

  // 1
  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(GlobalScope) // 2

  sharedFlow.onEach {
    println("Hello: $it")
  }.launchIn(GlobalScope)

  // 3
  sharedFlow.tryEmit(5)
  sharedFlow.tryEmit(3)

  // 4
  Thread.sleep(50)
}
Hello: 5
Emitting: 5
Hello: 3
Emitting: 3

Replaying Values

Now that you’ve created the basic relationship between the Flow and its subscribers, it’s time to take advantage of the internal mechanisms the SharedFlow provides to cache and replay the events.

fun main() {
  val sharedFlow = MutableSharedFlow<Int>(2)

  // Emitting events before subscribing
  sharedFlow.tryEmit(5)
  sharedFlow.tryEmit(3)

  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(GlobalScope)

  sharedFlow.onEach {
    println("Hello: $it")
  }.launchIn(GlobalScope)

  Thread.sleep(50)
}
Emitting: 5
Hello: 5
Emitting: 3
Hello: 3
fun main() {
  val sharedFlow = MutableSharedFlow<Int>(2)

  sharedFlow.tryEmit(5)
  sharedFlow.tryEmit(3)
  // Add a third event
  sharedFlow.tryEmit(1)

  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(GlobalScope)

  sharedFlow.onEach {
    println("Hello: $it")
  }.launchIn(GlobalScope)

  Thread.sleep(50)
}
Hello: 3
Emitting: 3
Hello: 1
Emitting: 1

Hot Streams

A significant difference from regular Flows is that a SharedFlow is hot by default. This means that when you create the Flow, it immediately starts working. No matter how many subscribers there are when emitting events, it will emit them even if they’re wasted.

fun main() {
  val sharedFlow = MutableSharedFlow<Int>() // remove the replay count

  sharedFlow.tryEmit(5)
  sharedFlow.tryEmit(3)
  sharedFlow.tryEmit(1)

  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(GlobalScope)

  sharedFlow.onEach {
    println("Hello: $it")
  }.launchIn(GlobalScope)

  Thread.sleep(50)
}
Process finished with exit code 0
fun main() {
  // 1
  val coroutineScope = CoroutineScope(Dispatchers.Default)
  val sharedFlow = MutableSharedFlow<Int>()

  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(coroutineScope)

  // 2
  coroutineScope.launch {
    sharedFlow.emit(5)
    sharedFlow.emit(3)
    sharedFlow.emit(1)

     // 3
    coroutineScope.cancel()
  }

  // 4
  while (coroutineScope.isActive) {

  }
}
Emitting: 5
Emitting: 3
Emitting: 1

Transforming a Flow to a SharedFlow

An alternative to creating a MutableSharedFlow is to start with a regular Flow and use shareIn to transform it and allow the fan-out behavior. Change the main snippet to the following:

fun main() {
  val coroutineScope = CoroutineScope(Dispatchers.Default)
  // 1
  val sharedFlow = flow {
    emit(5)
    emit(3)
    emit(1)

    Thread.sleep(50)
    coroutineScope.cancel()
  }.shareIn(coroutineScope, started = SharingStarted.Lazily) // 2

  sharedFlow.onEach {
    println("Emitting: $it")
  }.launchIn(coroutineScope)

  while (coroutineScope.isActive) {

  }
}
Emitting: 5
Emitting: 3
Emitting: 1

Process finished with exit code 0

SharedFlow Notes

There are a few last things to learn about the SharedFlow.

Building a StateFlow

An even more advanced version of a SharedFlow is the StateFlow. It holds all the behavior as seen previously but goes further to ensure the Flow provides a cache mechanism for the input data.

fun main() {
  val coroutineScope = CoroutineScope(Dispatchers.Default)

  val stateFlow = MutableStateFlow("Author: Filip") // here
  
  while (coroutineScope.isActive) {

  }
}
println(stateFlow.value) // 1

coroutineScope.launch {
  stateFlow.collect { // 2
    println(it)
  }
}
Author: Filip
Author: Filip
stateFlow.value = "Author: Luka" // 1

stateFlow.tryEmit("FPE: Max") // 2

coroutineScope.launch {
  stateFlow.emit("TE: Godfred") // 3
}

Thread.sleep(50)
coroutineScope.cancel()
Author: Filip
FPE: Max
TE: Godfred

StateFlow Notes

Like SharedFlow has a few important notes about it, so does StateFlow, given that it’s largely built on top of the SharedFlow API.

Key Points

Where to Go From Here?

You’ve learned everything you need to know about the Flow API, an excellent implementation of the observable/observer pattern and reactive streams!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.