Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building StateFlows with Jetpack Compose

Mohit S
September 02, 2022

Building StateFlows with Jetpack Compose

Mohit S

September 02, 2022
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Mohit Sarveiya
    Building State Flows with Compose
    @heyitsmohit

    View Slide

  2. Building State Flows with Compose
    ● How to setup and use Molecule

    View Slide

  3. Building State Flows with Compose
    ● How to setup and use Molecule

    ● Design Patterns

    View Slide

  4. Building State Flows with Compose
    ● How to setup and use Molecule

    ● Design Patterns

    ● Testing with Molecule

    View Slide

  5. Building State Flows with Compose
    ● How to setup and use Molecule

    ● Design Patterns

    ● Testing with Molecule

    ● Molecule Internals

    View Slide

  6. Building State Flows with Compose

    View Slide

  7. Building State Flows with Compose

    View Slide

  8. Compose vs Compose UI

    View Slide

  9. Compose
    ● General purpose tool for managing tree of nodes.

    View Slide

  10. Compose
    ● General purpose tool for managing tree of nodes.

    View Slide

  11. Compose
    ● General purpose tool for managing tree of nodes.
    Node can be anything

    View Slide

  12. Compose
    ● Compose Runtime

    ● Kotlin Complier Plugin

    ● State Snapshot System

    View Slide

  13. Compose UI
    ● UI Toolkit
    Views

    View Slide

  14. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    View Slide

  15. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    Compose Runtime

    View Slide

  16. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    Compose Runtime
    State Flow


    Flow

    View Slide

  17. val userFlow = db.userObservable()


    val balanceFlow = db.balanceObservable()

    View Slide

  18. @Composable


    fun ProfileScreen() {


    val user by userFlow.subscribeAsState(null)


    val balance by balanceFlow.subscribeAsState(0L)


    if (user
    ==
    null) {


    Text("Loading…")


    } else {


    Text("${user.name} - $balance")


    }


    }


    View Slide

  19. @Composable


    fun ProfileScreen() {


    val user by userFlow.subscribeAsState(null)


    val balance by balanceFlow.subscribeAsState(0L)


    if (user
    ==
    null) {


    Text("Loading…")


    } else {


    Text("${user.name} - $balance")


    }


    }


    View Slide

  20. @Composable


    fun ProfileScreen() {


    val user by userFlow.subscribeAsState(null)


    val balance by balanceFlow.subscribeAsState(0L)


    if (user
    ==
    null) {


    Text("Loading…")


    } else {


    Text("${user.name} - $balance")


    }


    }


    Undesired coupling

    View Slide

  21. @Composable


    fun ProfileScreen() {


    val user by userFlow.subscribeAsState(null)


    val balance by balanceFlow.subscribeAsState(0L)


    if (user
    ==
    null) {


    Text("Loading…")


    } else {


    Text("${user.name} - $balance")


    }


    }


    Not Reusable

    View Slide

  22. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {






    }


    }


    View Slide

  23. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {






    }


    }


    sealed interface ProfileModel {

    object Loading : ProfileModel

    data class Data(

    val name: String,

    val balance: Long,

    ): ProfileModel

    }

    View Slide

  24. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {




    }

    View Slide

  25. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {




    return combine(

    db.users().onStart { emit(null) },

    db.balances().onStart { emit(0L) },

    ) { user, balance
    ->


    }

    }


    }

    View Slide

  26. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {




    return combine(

    db.users().onStart { emit(null) },

    db.balances().onStart { emit(0L) },

    ) { user, balance
    ->


    }

    }


    }
    Can get complex if we add more streams

    View Slide

  27. Molecule
    @Composable

    fun MoleculePresenter(

    ...


    ): Model

    Compose Runtime
    State Flow


    Flow

    View Slide

  28. @Composable

    fun ProfilePresenter(

    userFlow: Flow,

    balanceFlow: Flow,

    ): ProfileModel {

    val user by userFlow.collectAsState(null)

    val balance by balanceFlow.collectAsState(0L)

    return if (user
    ==
    null) {

    Loading

    } else {

    Data(user.name, balance)

    }

    }

    View Slide

  29. @Composable

    fun ProfilePresenter(

    userFlow: Flow,

    balanceFlow: Flow,

    ): ProfileModel {

    val user by userFlow.collectAsState(null)

    val balance by balanceFlow.collectAsState(0L)

    return if (user
    ==
    null) {

    Loading

    } else {

    Data(user.name, balance)

    }

    }

    View Slide

  30. @Composable

    fun ProfilePresenter(

    userFlow: Flow,

    balanceFlow: Flow,

    ): ProfileModel {

    val user by userFlow.collectAsState(null)

    val balance by balanceFlow.collectAsState(0L)

    return if (user
    ==
    null) {

    Loading

    } else {

    Data(user.name, balance)

    }

    }

    View Slide

  31. val scope = CoroutineScope(Main)

    View Slide

  32. val scope = CoroutineScope(Main)

    scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  33. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  34. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  35. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  36. val scope = CoroutineScope(Main)


    scope.launchMolecule(

    clock = RecompositionClock.ContextClock,

    emitter = { value
    ->
    }

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  37. val scope = CoroutineScope(Main)


    scope.launchMolecule(

    clock = RecompositionClock.ContextClock,

    emitter = { value
    ->
    }

    ) {

    ProfilePresenter(events, randomService)

    }
    Will not respect back-pressure

    View Slide

  38. val models: Flow = moleculeFlow(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  39. val scope = CoroutineScope(Main)


    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }
    Frame Clock

    View Slide

  40. Composable
    Recomposition
    Enter the composition Leave the composition

    View Slide

  41. Composable
    Wait for next frame
    Enter the composition Leave the composition

    View Slide

  42. Composable
    Wait for next frame
    Enter the composition Leave the composition
    Monotonic Frame Clock

    View Slide

  43. Monotonic

    Frame Clock
    Pull Based System
    Pull Signal

    View Slide

  44. Monotonic

    Frame Clock
    Pull Based System
    Pull Signal
    Choreographer
    Performs Pull for UI

    View Slide

  45. val scope = CoroutineScope(Main)


    fun CoroutineScope.launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    //
    Pass context element to Recomposer

    }

    }

    View Slide

  46. interface MonotonicFrameClock : CoroutineContext.Element {

    suspend fun withFrameNanos(onFrame): R

    override val key: CoroutineContext.Key
    <*>
    get() = Key

    companion object Key : CoroutineContext.Key

    }

    View Slide

  47. Recomposition Clocks
    ● Context clock

    ● Immediate

    View Slide

  48. Context Clock
    ● Use MonotonicFrameClock from calling Coroutine


    Context

    ● Use Molecule with built in Android Frame Clock

    View Slide

  49. fun CoroutineScope.launchMolecule(
    ...
    ) {

    val clockContext = when (clock) {

    RecompositionClock.ContextClock
    ->
    EmptyCoroutineContext

    RecompositionClock.Immediate
    ->
    GatedFrameClock(this)

    }

    ...


    }

    View Slide

  50. fun CoroutineScope.launchMolecule(
    ...
    ) {

    val clockContext = when (clock) {

    RecompositionClock.ContextClock
    ->
    EmptyCoroutineContext

    RecompositionClock.Immediate
    ->
    GatedFrameClock(this)

    }

    ...


    }

    View Slide

  51. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    Frame clock provided by Compose

    View Slide

  52. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    val scope = CoroutineScope(dispatcher + clock)

    View Slide

  53. var value = 0

    scope.launchMolecule(RecompositionClock.ContextClock, { value = it }) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  54. scope.launchMolecule(
    ...
    ) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  55. scope.launchMolecule(
    ...
    ) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  56. dispatcher.advanceTimeBy(100)

    assertEquals(1, value)

    View Slide

  57. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  58. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  59. assertEquals(0, value)
    Initial Composition

    View Slide

  60. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    View Slide

  61. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View Slide

  62. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    assertEquals(1, value)

    View Slide

  63. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    val scope = CoroutineScope(dispatcher + clock)

    View Slide

  64. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    clock.sendFrame(0)


    assertEquals(1, value)

    View Slide

  65. Context Clock
    ● Use MonotonicFrameClock from calling Coroutine


    Context

    ● Use Molecule with built in Android Frame Clock

    View Slide

  66. Recomposition Clocks
    ● Context clock

    ● Immediate

    View Slide

  67. Gated Frame Clock
    ● Request for a frame immediately succeeds

    ● Can be stopped

    View Slide

  68. Immediate
    class GatedFrameClock(scope: CoroutineScope) : MonotonicFrameClock {


    val frameSends = Channel(CONFLATED)

    init {

    scope.launch {

    for (send in frameSends) sendFrame()

    }

    }

    
 ...


    }

    View Slide

  69. Immediate
    class GatedFrameClock(scope: CoroutineScope) : MonotonicFrameClock {


    val clock = BroadcastFrameClock {

    if (isRunning) frameSends.trySend(Unit).getOrThrow()

    }

    ...


    }

    View Slide

  70. Recomposition Clocks
    ● Context clock

    ● Immediate

    View Slide

  71. val scope = CoroutineScope(Main)


    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View Slide

  72. Launching Molecules
    ● Create coroutine scope

    ● Specify frame clock

    ● Provide Composable function

    View Slide

  73. Design Patterns

    View Slide

  74. View

    View Slide

  75. View Presenter
    Events

    View Slide

  76. View Presenter
    Events
    Model

    View Slide

  77. View Presenter
    Events
    Model
    Remote Service
    Result

    View Slide

  78. interface MoleculePresenter {



    @Composable

    fun models(events: Flow): Model

    }

    View Slide

  79. class ProfilePresenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    View Slide

  80. class ProfilePresenter(val repo: Repo): MoleculePresenter<
    ...
    > {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    View Slide

  81. @Composable

    fun models(events: Flow): ProfileModel {



    var data by remember { mutableStateOf(null) }

    LaunchedEffect(Unit) {

    data = repo.getProfileData()

    }

    }

    View Slide

  82. @Composable

    fun models(events: Flow): ProfileModel {

    ...


    return if (data
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(data)

    }

    }

    View Slide

  83. val models = scope.launchMolecule(
    ...
    ) {

    presenter.models(events)

    }
    Events Flow

    View Slide

  84. sealed interface ProfileEvents {

    object Edit: ProfileEvents

    }

    View Slide

  85. Channel
    Event
    Presenter

    View Slide

  86. @Composable

    fun models(events: Flow): ProfileModel {



    LaunchedEffect(events) {

    events.collect {

    when (it) {

    Events.Edit
    ->
    {
    ...
    }

    }

    }

    }

    }

    View Slide

  87. @Composable

    fun models(events: Flow): ProfileModel {



    CollectEffect(events) {

    when (it) {

    Events.ClickedSubmit
    ->
    {
    ...
    }

    }

    }

    }

    View Slide

  88. View Presenter
    Events
    Model
    Remote
    Service
    Result

    View Slide

  89. Molecule Presenter
    ● Separation of concerns

    ● Composite presenters

    View Slide

  90. Component 1
    Component 2
    Component 3

    View Slide

  91. class ProfilePresenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    Can get complex

    View Slide

  92. class Component1Presenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): Model {



    }



    }

    View Slide

  93. class ProfilePresenter(

    val component1Presenter: Component1Presenter,

    ...

    ): MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }

    }

    View Slide

  94. class ProfilePresenter(
    ...
    ): MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {

    val model = component1Presenter.models(events)

    }

    }

    View Slide

  95. View Presenter
    Events
    Model
    Remote Service
    Result

    View Slide

  96. Testing

    View Slide

  97. val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(randomService)

    }
    Testing

    View Slide

  98. Testing
    ● Turbine

    ● Molecule Testing Dependency [Deprecated]

    View Slide

  99. Turbine
    ● Queue based testing

    ● Use to be an extension in SQL Delight

    View Slide

  100. View Slide

  101. Turbine
    interface ReceiveTurbine {

    fun awaitEvent(): Event

    fun awaitError(): Throwable

    suspend fun awaitItem(): T

    ...


    }

    View Slide

  102. Turbine
    flow {

    emit("one")

    emit("two")

    }.test {

    assertEquals("one", awaitItem())

    assertEquals("two", awaitItem())

    }

    View Slide

  103. Turbine
    flow {

    emit("one")

    emit("two")

    }.test {

    assertEquals("one", awaitItem())

    assertEquals("two", awaitItem())

    }

    View Slide

  104. Turbine
    suspend fun Flow.test(

    validate: suspend ReceiveTurbine.()
    ->
    Unit,

    ) {

    coroutineScope {

    collectTurbineIn(this).apply {

    }

    }

    }

    View Slide

  105. Turbine
    fun Flow.collectTurbineIn(scope: CoroutineScope): Turbine {

    lateinit var channel: Channel

    val job = scope.launch(start = UNDISPATCHED) {

    channel = collectIntoChannel(this)

    }

    return ChannelTurbine(channel, job)

    }

    View Slide

  106. Turbine
    fun Flow.collectTurbineIn(scope: CoroutineScope): Turbine {

    lateinit var channel: Channel

    val job = scope.launch(start = UNDISPATCHED) {

    channel = collectIntoChannel(this)

    }

    return ChannelTurbine(channel, job)

    }

    View Slide

  107. Turbine
    fun Flow.collectTurbineIn(scope: CoroutineScope): Turbine {

    lateinit var channel: Channel

    val job = scope.launch(start = UNDISPATCHED) {

    channel = collectIntoChannel(this)

    }

    return ChannelTurbine(channel, job)

    }

    View Slide

  108. Turbine
    class ChannelTurbine(

    channel: Channel = Channel(UNLIMITED)

    ) : Turbine {

    suspend fun awaitItem(): T = channel.awaitItem()

    }

    View Slide

  109. Turbine
    val channel = Turbine()

    listOf(1, 2, 3).forEach { channel.add(it) }

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    View Slide

  110. val channel = Turbine()

    listOf(1, 2, 3).forEach { channel.add(it) }

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    Turbine

    View Slide

  111. Turbine
    val channel = Turbine()

    listOf(1, 2, 3).forEach { channel.add(it) }

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    View Slide

  112. val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    profilePresenter.models(events)

    }
    Testing

    View Slide

  113. Testing Cases
    ● Verify correct models are created

    ● Verify side effects from events

    View Slide

  114. Testing
    ● runBlocking

    ● Immediate Frame Clock

    ● Use Turbine

    View Slide

  115. Molecule Testing
    @Test

    fun `should get success model`() = runBlocking {

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    }

    }

    View Slide

  116. Molecule Testing
    @Test

    fun `should get success model`() = runBlocking {

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    assertEquals(ProfileModel.Loading, awaitItem())

    ...


    }

    }

    View Slide

  117. Molecule Testing
    @Test

    fun `should get success model`() = runBlocking {


    val events = MutableSharedFlow()

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    }

    }

    View Slide

  118. Molecule Testing
    @Test

    fun `should get success model`() = runBlocking {

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    events.emit(EditProfile)

    }

    }

    View Slide

  119. Molecule Testing
    @Test

    fun `should get success model`() = runBlocking {

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    events.emit(EditProfile)

    //
    Assertions

    }

    }

    View Slide

  120. Testing
    ● runBlocking

    ● Immediate Frame Clock

    ● Use Turbine

    View Slide

  121. Molecule Internals

    View Slide

  122. val scope = CoroutineScope(Main)


    fun CoroutineScope.launchMolecule(
    ...
    ) {

    //
    Setup Recomposer

    }

    View Slide

  123. Internals
    ● Compose Compiler & Runtime

    ● How Molecule hooks into Compose Runtime

    View Slide

  124. Compose Components
    ● Composition

    ● Recomposer

    ● Applier

    View Slide

  125. Compose Internals
    class Foo {

    fun bar() {

    ...

    }

    }
    Code Parsing
    Analysis

    &

    Resolve
    IR

    View Slide

  126. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Compose Internals

    View Slide

  127. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Creates Deferred Change
    Compose Internals

    View Slide

  128. @Composable

    fun Greeting(name: String, $composer: Composer
    <*>
    ) {

    Text(name)

    }

    Connects to Compose Runtime
    Compose Internals

    View Slide

  129. @Composable

    fun Greeting(name: String, $composer: Composer
    <*>
    ) {

    Text(name, $composer)

    }

    Calling context is passed along
    Compose Internals

    View Slide

  130. suspend fun getData(payload: Payload) {

    ...


    }

    Compose Internals

    View Slide

  131. suspend fun getData(payload: Payload, $cont: Continuation) {

    anotherMethod($cont)

    }

    Compose Internals

    View Slide

  132. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Creates Deferred Change
    Composition

    View Slide

  133. @Composable

    inline fun Layout(
    ...
    ) {

    ReusableComposeNode>>
    (

    update = {

    set(measurePolicy, ComposeUiNode.SetMeasurePolicy)

    set(density, ComposeUiNode.SetDensity)

    ...


    },

    )

    }
    Composition
    Teaching the runtime

    View Slide

  134. @Composable

    inline fun ResuableComposeNode(
    ...
    ) {

    ...


    currentComposer.startReusableNode()

    ...

    currentComposer.createNode(factory)

    }

    Composition
    Delegate to composer

    View Slide

  135. Composition
    @Composable

    inline fun remember(
    ...
    ) {

    ...


    currentComposer.cache(
    ...
    )

    }

    View Slide

  136. @Composable

    inline fun ResuableComposeNode(
    ...
    ) {

    ...


    currentComposer.startReusableNode()

    ...

    currentComposer.createNode(factory)

    }

    Composition
    Changes List
    Change(…)
    Change(…)
    Change(…)

    View Slide

  137. Composition
    internal typealias Change = (

    applier: Applier
    <*>
    ,

    slots: SlotWriter,

    rememberManager: RememberManager

    )
    ->
    Unit

    View Slide

  138. Composition
    Changes List
    Change(…)
    Change(…)
    Change(…)
    Slot table
    Applier

    View Slide

  139. Composition
    class UiApplier(…): AbstractApplier {

    fun insertBottomUp(
    ...
    )

    fun insertTopDown(
    ...
    )

    fun move(
    ...
    )

    fun onClear(
    ...
    )

    fun onEndChanges(
    ...
    )

    fun remove(
    ...
    )

    }

    Applier in Compose UI

    View Slide

  140. Entry Points for Client Libraries
    ● Create a recomposer

    ● Specify Applier

    ● Call setContent with composable function

    View Slide

  141. Recomposer
    ● Trigger recompositions

    ● Determine which thread to compose or recompose on

    View Slide

  142. val contextWithClock = currentThreadContext +

    (pausableClock
    ?:
    EmptyCoroutineContext)


    val recomposer = Recomposer(contextWithClock)

    Compose UI Example

    View Slide

  143. fun launchMolecule(clock: RecompositionClock, body: @Composable ()
    ->
    T) {

    with(this + clockContext) {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(UnitApplier, recomposer)

    launch(start = UNDISPATCHED) {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    }
    Molecule Internals

    View Slide

  144. fun launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(UnitApplier, recomposer)

    launch(start = UNDISPATCHED) {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    }
    Molecule Internals

    View Slide

  145. Recomposer
    ● Creates coroutine Job

    ● Applies changes for recomposition using context

    ● Cancels composition or recompositions when shutdown

    View Slide

  146. fun launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(UnitApplier, recomposer)

    launch(start = UNDISPATCHED) {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    }
    Molecule Internals

    View Slide

  147. fun launchMolecule(@Composable body: ()
    ->
    T) {

    with(this + clockContext) {

    ...


    composition.setContent {

    body()

    }

    }

    }
    Molecule Internals

    View Slide

  148. fun launchMolecule(emitter: (value: T)
    ->
    Unit) {

    with(this + clockContext) {

    ...


    composition.setContent {

    emitter(body())

    }

    }

    }
    Molecule Internals

    View Slide

  149. Molecule Internals
    var flow: MutableStateFlow? = null

    launchMolecule(

    emitter = { value
    ->


    outputFlow.value = value

    },

    ...


    )

    View Slide

  150. val models: StateFlow = scope.launchMolecule(…) {

    profilePresenter.models(events)

    }

    models.collect {

    ...


    }

    View Slide

  151. Building State Flows with Compose
    ● How to setup and use Molecule

    ● Design Patterns

    ● Testing with Molecule

    ● Molecule Internals

    View Slide

  152. Thank You!
    www.codingwithmohit.com
    @heyitsmohit

    View Slide