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 full-size slide

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

    View full-size slide

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

    ● Design Patterns

    View full-size slide

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

    ● Design Patterns

    ● Testing with Molecule

    View full-size slide

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

    ● Design Patterns

    ● Testing with Molecule

    ● Molecule Internals

    View full-size slide

  6. Building State Flows with Compose

    View full-size slide

  7. Building State Flows with Compose

    View full-size slide

  8. Compose vs Compose UI

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  12. Compose
    ● Compose Runtime

    ● Kotlin Complier Plugin

    ● State Snapshot System

    View full-size slide

  13. Compose UI
    ● UI Toolkit
    Views

    View full-size slide

  14. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    View full-size slide

  15. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    Compose Runtime

    View full-size slide

  16. Molecule
    @Composable

    fun MoleculePresenter(

    events: Flow,

    ): Model

    Compose Runtime
    State Flow


    Flow

    View full-size slide

  17. val userFlow = db.userObservable()


    val balanceFlow = db.balanceObservable()

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

  22. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {






    }


    }


    View full-size 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 full-size slide

  24. class ProfilePresenter(val db: Db) {


    fun transform(events: Flow)
    :
    Flow {




    }

    View full-size 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 full-size 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 full-size slide

  27. Molecule
    @Composable

    fun MoleculePresenter(

    ...


    ): Model

    Compose Runtime
    State Flow


    Flow

    View full-size 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 full-size 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 full-size 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 full-size slide

  31. val scope = CoroutineScope(Main)

    View full-size slide

  32. val scope = CoroutineScope(Main)

    scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  33. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  34. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  35. val scope = CoroutineScope(Main)

    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  36. val scope = CoroutineScope(Main)


    scope.launchMolecule(

    clock = RecompositionClock.ContextClock,

    emitter = { value
    ->
    }

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  37. val scope = CoroutineScope(Main)


    scope.launchMolecule(

    clock = RecompositionClock.ContextClock,

    emitter = { value
    ->
    }

    ) {

    ProfilePresenter(events, randomService)

    }
    Will not respect back-pressure

    View full-size slide

  38. val models: Flow = moleculeFlow(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  39. val scope = CoroutineScope(Main)


    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }
    Frame Clock

    View full-size slide

  40. Composable
    Recomposition
    Enter the composition Leave the composition

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. Monotonic

    Frame Clock
    Pull Based System
    Pull Signal

    View full-size slide

  44. Monotonic

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

    View full-size slide

  45. val scope = CoroutineScope(Main)


    fun CoroutineScope.launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    //
    Pass context element to Recomposer

    }

    }

    View full-size slide

  46. interface MonotonicFrameClock : CoroutineContext.Element {

    suspend fun withFrameNanos(onFrame): R

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

    companion object Key : CoroutineContext.Key

    }

    View full-size slide

  47. Recomposition Clocks
    ● Context clock

    ● Immediate

    View full-size slide

  48. Context Clock
    ● Use MonotonicFrameClock from calling Coroutine


    Context

    ● Use Molecule with built in Android Frame Clock

    View full-size slide

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

    val clockContext = when (clock) {

    RecompositionClock.ContextClock
    ->
    EmptyCoroutineContext

    RecompositionClock.Immediate
    ->
    GatedFrameClock(this)

    }

    ...


    }

    View full-size slide

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

    val clockContext = when (clock) {

    RecompositionClock.ContextClock
    ->
    EmptyCoroutineContext

    RecompositionClock.Immediate
    ->
    GatedFrameClock(this)

    }

    ...


    }

    View full-size slide

  51. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    Frame clock provided by Compose

    View full-size slide

  52. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    val scope = CoroutineScope(dispatcher + clock)

    View full-size 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 full-size slide

  54. scope.launchMolecule(
    ...
    ) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View full-size slide

  55. scope.launchMolecule(
    ...
    ) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View full-size slide

  56. dispatcher.advanceTimeBy(100)

    assertEquals(1, value)

    View full-size slide

  57. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View full-size slide

  58. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View full-size slide

  59. assertEquals(0, value)
    Initial Composition

    View full-size slide

  60. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    View full-size slide

  61. scope.launchMolecule(RecompositionClock.ContextClock) {

    var count by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {

    while (true) {

    delay(100)

    count
    ++


    }

    }

    count

    }

    View full-size slide

  62. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    assertEquals(1, value)

    View full-size slide

  63. val dispatcher = TestCoroutineDispatcher()

    val clock = BroadcastFrameClock()

    val scope = CoroutineScope(dispatcher + clock)

    View full-size slide

  64. assertEquals(0, value)


    dispatcher.advanceTimeBy(100)


    clock.sendFrame(0)


    assertEquals(1, value)

    View full-size slide

  65. Context Clock
    ● Use MonotonicFrameClock from calling Coroutine


    Context

    ● Use Molecule with built in Android Frame Clock

    View full-size slide

  66. Recomposition Clocks
    ● Context clock

    ● Immediate

    View full-size slide

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

    ● Can be stopped

    View full-size slide

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


    val frameSends = Channel(CONFLATED)

    init {

    scope.launch {

    for (send in frameSends) sendFrame()

    }

    }

    
 ...


    }

    View full-size slide

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


    val clock = BroadcastFrameClock {

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

    }

    ...


    }

    View full-size slide

  70. Recomposition Clocks
    ● Context clock

    ● Immediate

    View full-size slide

  71. val scope = CoroutineScope(Main)


    val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(events, randomService)

    }

    View full-size slide

  72. Launching Molecules
    ● Create coroutine scope

    ● Specify frame clock

    ● Provide Composable function

    View full-size slide

  73. Design Patterns

    View full-size slide

  74. View Presenter
    Events

    View full-size slide

  75. View Presenter
    Events
    Model

    View full-size slide

  76. View Presenter
    Events
    Model
    Remote Service
    Result

    View full-size slide

  77. interface MoleculePresenter {



    @Composable

    fun models(events: Flow): Model

    }

    View full-size slide

  78. class ProfilePresenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    View full-size slide

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



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    View full-size slide

  80. @Composable

    fun models(events: Flow): ProfileModel {



    var data by remember { mutableStateOf(null) }

    LaunchedEffect(Unit) {

    data = repo.getProfileData()

    }

    }

    View full-size slide

  81. @Composable

    fun models(events: Flow): ProfileModel {

    ...


    return if (data
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(data)

    }

    }

    View full-size slide

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

    presenter.models(events)

    }
    Events Flow

    View full-size slide

  83. sealed interface ProfileEvents {

    object Edit: ProfileEvents

    }

    View full-size slide

  84. Channel
    Event
    Presenter

    View full-size slide

  85. @Composable

    fun models(events: Flow): ProfileModel {



    LaunchedEffect(events) {

    events.collect {

    when (it) {

    Events.Edit
    ->
    {
    ...
    }

    }

    }

    }

    }

    View full-size slide

  86. @Composable

    fun models(events: Flow): ProfileModel {



    CollectEffect(events) {

    when (it) {

    Events.ClickedSubmit
    ->
    {
    ...
    }

    }

    }

    }

    View full-size slide

  87. View Presenter
    Events
    Model
    Remote
    Service
    Result

    View full-size slide

  88. Molecule Presenter
    ● Separation of concerns

    ● Composite presenters

    View full-size slide

  89. Component 1
    Component 2
    Component 3

    View full-size slide

  90. class ProfilePresenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }



    }

    Can get complex

    View full-size slide

  91. class Component1Presenter: MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): Model {



    }



    }

    View full-size slide

  92. class ProfilePresenter(

    val component1Presenter: Component1Presenter,

    ...

    ): MoleculePresenter {



    @Composable

    override

    fun models(events: Flow): ProfileModel {



    }

    }

    View full-size slide

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



    @Composable

    override

    fun models(events: Flow): ProfileModel {

    val model = component1Presenter.models(events)

    }

    }

    View full-size slide

  94. View Presenter
    Events
    Model
    Remote Service
    Result

    View full-size slide

  95. val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    ProfilePresenter(randomService)

    }
    Testing

    View full-size slide

  96. Testing
    ● Turbine

    ● Molecule Testing Dependency [Deprecated]

    View full-size slide

  97. Turbine
    ● Queue based testing

    ● Use to be an extension in SQL Delight

    View full-size slide

  98. Turbine
    interface ReceiveTurbine {

    fun awaitEvent(): Event

    fun awaitError(): Throwable

    suspend fun awaitItem(): T

    ...


    }

    View full-size slide

  99. Turbine
    flow {

    emit("one")

    emit("two")

    }.test {

    assertEquals("one", awaitItem())

    assertEquals("two", awaitItem())

    }

    View full-size slide

  100. Turbine
    flow {

    emit("one")

    emit("two")

    }.test {

    assertEquals("one", awaitItem())

    assertEquals("two", awaitItem())

    }

    View full-size slide

  101. Turbine
    suspend fun Flow.test(

    validate: suspend ReceiveTurbine.()
    ->
    Unit,

    ) {

    coroutineScope {

    collectTurbineIn(this).apply {

    }

    }

    }

    View full-size slide

  102. 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 full-size slide

  103. 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 full-size slide

  104. 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 full-size slide

  105. Turbine
    class ChannelTurbine(

    channel: Channel = Channel(UNLIMITED)

    ) : Turbine {

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

    }

    View full-size slide

  106. Turbine
    val channel = Turbine()

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

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    View full-size slide

  107. val channel = Turbine()

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

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    Turbine

    View full-size slide

  108. Turbine
    val channel = Turbine()

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

    channel.skipItems(2)

    assertEquals(3, channel.awaitItem())

    View full-size slide

  109. val models: StateFlow = scope.launchMolecule(

    clock = RecompositionClock.ContextClock

    ) {

    profilePresenter.models(events)

    }
    Testing

    View full-size slide

  110. Testing Cases
    ● Verify correct models are created

    ● Verify side effects from events

    View full-size slide

  111. Testing
    ● runBlocking

    ● Immediate Frame Clock

    ● Use Turbine

    View full-size slide

  112. Molecule Testing
    @Test

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

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    }

    }

    View full-size slide

  113. Molecule Testing
    @Test

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

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    assertEquals(ProfileModel.Loading, awaitItem())

    ...


    }

    }

    View full-size slide

  114. Molecule Testing
    @Test

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


    val events = MutableSharedFlow()

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    }

    }

    View full-size slide

  115. Molecule Testing
    @Test

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

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    events.emit(EditProfile)

    }

    }

    View full-size slide

  116. Molecule Testing
    @Test

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

    launchMolecule(RecompositionClock.Immediate) {

    profilePresenter.models(events)

    }.test {

    events.emit(EditProfile)

    //
    Assertions

    }

    }

    View full-size slide

  117. Testing
    ● runBlocking

    ● Immediate Frame Clock

    ● Use Turbine

    View full-size slide

  118. Molecule Internals

    View full-size slide

  119. val scope = CoroutineScope(Main)


    fun CoroutineScope.launchMolecule(
    ...
    ) {

    //
    Setup Recomposer

    }

    View full-size slide

  120. Internals
    ● Compose Compiler & Runtime

    ● How Molecule hooks into Compose Runtime

    View full-size slide

  121. Compose Components
    ● Composition

    ● Recomposer

    ● Applier

    View full-size slide

  122. Compose Internals
    class Foo {

    fun bar() {

    ...

    }

    }
    Code Parsing
    Analysis

    &

    Resolve
    IR

    View full-size slide

  123. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Compose Internals

    View full-size slide

  124. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Creates Deferred Change
    Compose Internals

    View full-size slide

  125. @Composable

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

    Text(name)

    }

    Connects to Compose Runtime
    Compose Internals

    View full-size slide

  126. @Composable

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

    Text(name, $composer)

    }

    Calling context is passed along
    Compose Internals

    View full-size slide

  127. suspend fun getData(payload: Payload) {

    ...


    }

    Compose Internals

    View full-size slide

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

    anotherMethod($cont)

    }

    Compose Internals

    View full-size slide

  129. @Composable

    fun Greeting(name: String) {

    Text(name)

    }

    Creates Deferred Change
    Composition

    View full-size slide

  130. @Composable

    inline fun Layout(
    ...
    ) {

    ReusableComposeNode>>
    (

    update = {

    set(measurePolicy, ComposeUiNode.SetMeasurePolicy)

    set(density, ComposeUiNode.SetDensity)

    ...


    },

    )

    }
    Composition
    Teaching the runtime

    View full-size slide

  131. @Composable

    inline fun ResuableComposeNode(
    ...
    ) {

    ...


    currentComposer.startReusableNode()

    ...

    currentComposer.createNode(factory)

    }

    Composition
    Delegate to composer

    View full-size slide

  132. Composition
    @Composable

    inline fun remember(
    ...
    ) {

    ...


    currentComposer.cache(
    ...
    )

    }

    View full-size slide

  133. @Composable

    inline fun ResuableComposeNode(
    ...
    ) {

    ...


    currentComposer.startReusableNode()

    ...

    currentComposer.createNode(factory)

    }

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

    View full-size slide

  134. Composition
    internal typealias Change = (

    applier: Applier
    <*>
    ,

    slots: SlotWriter,

    rememberManager: RememberManager

    )
    ->
    Unit

    View full-size slide

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

    View full-size slide

  136. Composition
    class UiApplier(…): AbstractApplier {

    fun insertBottomUp(
    ...
    )

    fun insertTopDown(
    ...
    )

    fun move(
    ...
    )

    fun onClear(
    ...
    )

    fun onEndChanges(
    ...
    )

    fun remove(
    ...
    )

    }

    Applier in Compose UI

    View full-size slide

  137. Entry Points for Client Libraries
    ● Create a recomposer

    ● Specify Applier

    ● Call setContent with composable function

    View full-size slide

  138. Recomposer
    ● Trigger recompositions

    ● Determine which thread to compose or recompose on

    View full-size slide

  139. val contextWithClock = currentThreadContext +

    (pausableClock
    ?:
    EmptyCoroutineContext)


    val recomposer = Recomposer(contextWithClock)

    Compose UI Example

    View full-size slide

  140. 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 full-size slide

  141. fun launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(UnitApplier, recomposer)

    launch(start = UNDISPATCHED) {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    }
    Molecule Internals

    View full-size slide

  142. Recomposer
    ● Creates coroutine Job

    ● Applies changes for recomposition using context

    ● Cancels composition or recompositions when shutdown

    View full-size slide

  143. fun launchMolecule(
    ...
    ) {

    with(this + clockContext) {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(UnitApplier, recomposer)

    launch(start = UNDISPATCHED) {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    }
    Molecule Internals

    View full-size slide

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

    with(this + clockContext) {

    ...


    composition.setContent {

    body()

    }

    }

    }
    Molecule Internals

    View full-size slide

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

    with(this + clockContext) {

    ...


    composition.setContent {

    emitter(body())

    }

    }

    }
    Molecule Internals

    View full-size slide

  146. Molecule Internals
    var flow: MutableStateFlow? = null

    launchMolecule(

    emitter = { value
    ->


    outputFlow.value = value

    },

    ...


    )

    View full-size slide

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

    profilePresenter.models(events)

    }

    models.collect {

    ...


    }

    View full-size slide

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

    ● Design Patterns

    ● Testing with Molecule

    ● Molecule Internals

    View full-size slide

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

    View full-size slide