Molecule: Build a StateFlow stream using Jetpack Compose

Let’s look at a snippet from Jetpack Compose:

@Composable
fun MessageCard(message: Message) {
  Column {
    Text(text = message.author)
    Text(text = message.body)
  }
}

The most interesting part of this code is that it is actually reactive. Its reactiveness is abstracted away by the @Composable annotation and delegated variables. For instance, say the parameter message is delegated to a MutableState. When message is updated, the method MessageCard’s current snapshot state will become dirty and the next time the clock ticks, the method will emit and it will update the two text views in place. The equivalent in RxJava or Kotlin flows could be that message: Message is a Observable<Message> or Flow<Message>, and that MessageCard() returns Observable<View> or Flow<View>.

On top of that, the androix.compose.runtime provides APIs to consume Kotlin flows within composable methods, such as StateFlow<T>.collectAsState: State<T>.
This means we can write code as such:

@Composable
fun profilePresenter(
  userFlow: Flow<User>,
  balanceFlow: Flow<Long>,
): ProfileModel {
  val user by userFlow.collectAsState(null)
  val balance by balanceFlow.collectAsState(0L)

  return if (user == null) {
    Loading
  } else {
    Data(user.name, balance)
  }
}

No complex chained RxJava or Kotlin flow operators. The abstraction offered by Compose allows us to write imperative and reactive code with simple logic such as if/else, when statements, or for loops.

Now, the question is: How am I going to use my composable method in my non-composable environment? And the answer is: Molecule!
Back in November 2021, Jake wrote about Molecule on this blog when it was still under development. A few weeks ago, we released 0.4.0 which we are using in production with great satisfaction.

Molecule does one thing: it transforms a composable method into a Flow or a StateFlow via the following APIs:

/**
 * Create a [Flow] which will continually recompose `body` to produce a stream of [T] values
 * when collected.
 */
fun <T> moleculeFlow(
  clock: RecompositionClock,
  body: @Composable () -> T,
): Flow<T>

/**
 * Launch a coroutine into this [CoroutineScope] which will continually recompose `body`
 * to produce a [StateFlow] stream of [T] values.
 */
fun <T> CoroutineScope.launchMolecule(
  clock: RecompositionClock,
  body: @Composable () -> T,
): StateFlow<T>

Molecule lets you pass two clocks for two different recomposition behavior:

Writing imperative code without losing reactiveness is amazing. At Cash App, our new presenters are written as composable methods. They are simple to write, simple to review, and simple to test!
Have a look at our README to get started!