Skip to content

Commit 5fc5649

Browse files
authored
Merge pull request #51 from cashapp/jw/flow/2021-11-10
Re-add flow factory
2 parents a695d95 + 892821c commit 5fc5649

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

README.md

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Molecule
22

3-
Build a `StateFlow` stream using Jetpack Compose[^1].
3+
Build a `StateFlow` or `Flow` stream using Jetpack Compose[^1].
44

55
```kotlin
66
fun CoroutineScope.launchCounter(): StateFlow<Int> = launchMolecule {
@@ -108,7 +108,7 @@ This model-producing composable function can be run with `launchMolecule`.
108108
```kotlin
109109
val userFlow = db.users()
110110
val balanceFlow = db.balances()
111-
val models = scope.launchMolecule {
111+
val models: StateFlow<ProfileModel> = scope.launchMolecule {
112112
ProfilePresenter(userFlow, balanceFlow)
113113
}
114114
```
@@ -126,6 +126,40 @@ fun Profile(models: StateFlow<ProfileModel>) {
126126
}
127127
```
128128

129+
For more information see [the `launchMolecule` documentation](https://cashapp.github.io/molecule/docs/latest/molecule-runtime/molecule-runtime/app.cash.molecule/launch-molecule.html).
130+
131+
### Flow
132+
133+
In addition to `StateFlow`s, Molecule can create regular `Flow`s.
134+
The flow-returning function does not require a `CoroutineScope` as it will be inherited from the collector.
135+
136+
Here is the presenter example updated to use a regular `Flow`:
137+
```kotlin
138+
val userFlow = db.users()
139+
val balanceFlow = db.balances()
140+
val models: Flow<ProfileModel> = moleculeFlow {
141+
ProfilePresenter(userFlow, balanceFlow)
142+
}
143+
```
144+
145+
And the counter example:
146+
```kotlin
147+
fun counter(): Flow<Int> = moleculeFlow {
148+
val count by remember { mutableStateOf(0) }
149+
150+
LaunchedEffect(Unit) {
151+
while (true) {
152+
delay(1_000)
153+
count++
154+
}
155+
}
156+
157+
count
158+
}
159+
```
160+
161+
For more information see [the `moleculeFlow` documentation](https://cashapp.github.io/molecule/docs/latest/molecule-runtime/molecule-runtime/app.cash.molecule/molecule-flow.html).
162+
129163
## Usage
130164

131165
Add the buildscript dependency and apply the plugin to every module which wants to call `launchMolecule` or define `@Composable` functions for use with Molecule.
@@ -168,15 +202,16 @@ apply plugin: 'app.cash.molecule'
168202

169203
### Frame Clock
170204

171-
The entrypoint to the library is [the `launchMolecule` function](https://cashapp.github.io/molecule/docs/latest/molecule-runtime/molecule-runtime/app.cash.molecule/launch-molecule.html) which is an extension on `CoroutineScope`.
172-
That scope must contain a `MonotonicFrameClock` key which is used to determine when recomposition occurs and a new value is produced.
205+
Molecule requires a `MonotonicFrameClock` key in your `CoroutineScope`.
206+
This applies to [the `launchMolecule` extension's](https://cashapp.github.io/molecule/docs/latest/molecule-runtime/molecule-runtime/app.cash.molecule/launch-molecule.html) receiver and the scope in which you collect [the `moleculeFlow` function]()-returned flow.
207+
The clock is used to determine when recomposition occurs and a new value is produced.
173208

174209
On Android, [`AndroidUiDispatcher.Main`](https://cashapp.github.io/molecule/docs/latest/molecule-runtime/molecule-runtime/app.cash.molecule/-android-ui-dispatcher/-companion/-main.html) can be used for running your composables on the main thread with recomposition synchronized to the frame rate.
175210
For any other rate or to recompose on a background thread, create a [`BroadcastFrameClock`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/BroadcastFrameClock) and a timer to invoke its `sendFrame` function at your desired rate.
176211

177212
### Testing
178213

179-
While the created `StateFlow` can be tested normally, the use of the frame clock to control recomposition makes it harder than it should be.
214+
While the created `StateFlow`s and `Flow`s can be tested normally, the use of the frame clock to control recomposition makes it harder than it should be.
180215
The 'molecule-testing' dependency provides a `testMolecule` function which simplifies your test code by managing the threading, coroutine scope, and frame clock for you.
181216

182217
```kotlin

molecule/molecule-runtime/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginKt
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
23

34
apply plugin: 'com.android.library'
45
apply plugin: 'org.jetbrains.kotlin.android'
@@ -43,6 +44,12 @@ android {
4344
}
4445
}
4546

47+
tasks.withType(KotlinCompile).configureEach {
48+
kotlinOptions {
49+
freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
50+
}
51+
}
52+
4653
spotless {
4754
kotlin {
4855
targetExclude(

molecule/molecule-runtime/src/main/java/app/cash/molecule/molecule.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,26 @@ import androidx.compose.runtime.Recomposer
2222
import androidx.compose.runtime.snapshots.Snapshot
2323
import kotlinx.coroutines.CoroutineScope
2424
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
25+
import kotlinx.coroutines.ExperimentalCoroutinesApi
26+
import kotlinx.coroutines.flow.Flow
2527
import kotlinx.coroutines.flow.MutableStateFlow
2628
import kotlinx.coroutines.flow.StateFlow
29+
import kotlinx.coroutines.flow.channelFlow
2730
import kotlinx.coroutines.job
2831
import kotlinx.coroutines.launch
2932

33+
@OptIn(ExperimentalCoroutinesApi::class) // Marked as stable in kotlinx.coroutines 1.6.
34+
fun <T> moleculeFlow(body: @Composable () -> T): Flow<T> {
35+
return channelFlow {
36+
launchMolecule(
37+
emitter = {
38+
trySend(it).getOrThrow()
39+
},
40+
body = body,
41+
)
42+
}
43+
}
44+
3045
fun <T> CoroutineScope.launchMolecule(
3146
body: @Composable () -> T,
3247
): StateFlow<T> {

0 commit comments

Comments
 (0)