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

Using Jetpack Compose with Square’s Molecule Library

Mohit S
February 16, 2022

Using Jetpack Compose with Square’s Molecule Library

Building StateFlows with Jetpack Compose.

Mohit S

February 16, 2022
Tweet

More Decks by Mohit S

Other Decks in Technology

Transcript

  1. Mohit Sarveiya
    Using Jetpack Compose with Square's Molecule Library

    @heyitsmohit

    View Slide

  2. Using Jetpack Compose with Square's Molecule Library
    ● How to setup and use Molecule

    ● Testing with Molecule

    ● Molecule Internals

    ● Example App

    View Slide

  3. Molecule
    Problems, Purpose

    View Slide

  4. Problem

    View Slide

  5. Problem
    Flow 1

    View Slide

  6. Problem
    Flow 1
    Flow 2

    View Slide

  7. Problem
    @Composable

    fun Profile(flow1, flow2) {

    val state1 by flow1.collectAsState(null)

    val state2 by flow2.collectAsState(0L)

    ...


    }

    View Slide

  8. Problem
    @Composable

    fun Profile(viewModel: ProfilesViewModel) {

    val state1 by flow1.collectAsState(null)

    val state2 by flow2.collectAsState(0L)

    ...


    }

    Mixes business logic with view

    View Slide

  9. Approach
    Presenter

    (Composable)

    View Slide

  10. Approach
    Events
    Presenter

    (Composable)

    View Slide

  11. Approach
    Events
    Presenter

    (Composable)
    Molecule

    View Slide

  12. Approach
    Events
    Presenter

    (Composable)
    Molecule

    View Slide

  13. Approach
    Events
    Presenter

    (Composable)
    Molecule
    State Flow

    View Slide

  14. Molecule
    Purpose

    Build a StateFlow using Jetpack Compose

    View Slide

  15. Compose vs Compose UI

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. Compose UI
    ● UI Toolkit

    View Slide

  20. Compose UI
    ● UI Toolkit
    Views, ViewGroup, etc…

    View Slide

  21. Resources
    ● A Jetpack Compose by any other name - Jake Wharton

    ● Compose From First Principles - Leland Richardson

    View Slide

  22. Molecule
    ● Kotlin Compiler Plugin

    ● Continually recomposes based on clock that controls


    when recomposition occurs

    View Slide

  23. How to use Molecule
    Setup & Launch molecules

    View Slide

  24. Problem
    Flow 1
    Flow 2

    View Slide

  25. Setup
    View

    View Slide

  26. Setup
    View Molecule

    View Slide

  27. Setup
    View Molecule
    Presenter

    (Composable)
    State

    View Slide

  28. Setup
    View Molecule
    Presenter

    (Composable)
    Events

    View Slide

  29. Presenter
    @Composable

    fun profilePresenter(
    ...
    ) {

    }

    View Slide

  30. Model
    sealed class ProfilesModel {

    object Loading: ProfilesModel()

    data class Data(
    ...
    ): ProfilesModel()

    }

    View Slide

  31. Presenter
    @Composable

    fun profilePresenter(
    ...
    ): ProfileModel {

    }

    View Slide

  32. Problem
    Flow 1
    Flow 2

    View Slide

  33. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  34. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  35. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  36. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  37. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  38. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  39. Setup
    Molecule
    Presenter

    (Composable)

    View Slide

  40. Setup
    Molecule
    Presenter

    (Composable)
    StateFlow

    View Slide

  41. Launching Molecule
    ● Define a coroutine scope

    View Slide

  42. Launching Molecule
    val scope = CoroutineScope(Dispatchers.Main)

    val models: StateFlow = scope.launchMolecule {

    userPresenter(postsFlow, likesFlow)

    }

    View Slide

  43. Launching Molecule
    ● Define a coroutine scope

    ● Specify frame clock

    View Slide

  44. Monotomic Frame Clock
    ● Controls when recomposition occurs

    ● AndroidUiDispatcher

    ● BroadcastFrameClock

    View Slide

  45. Launching Molecule
    val scope = CoroutineScope(

    Dispatchers.Main + AndroidUiDispatcher.Main

    )

    View Slide

  46. Launching Molecule
    ● Define a coroutine scope

    ● Specify frame clock

    ● Use launchMolecule method to return state from presenter

    View Slide

  47. Launching Molecule
    val models: StateFlow = scope.launchMolecule {

    profilePresenter(postsFlow, likesFlow)

    }

    View Slide

  48. Setup
    View Molecule

    View Slide

  49. View
    @Composable

    fun Profile(models: StateFlow) {


    val model by models.collectAsState()

    when(model) {

    UsersModel.Loading
    -> ...




    UsersModel.Success
    -> ...

    }



    }

    View Slide

  50. View
    @Composable

    fun Profile(models: StateFlow) {


    val model by models.collectAsState()

    when(model) {

    UsersModel.Loading
    -> ...




    UsersModel.Success
    -> ...

    }



    }

    View Slide

  51. View
    @Composable

    fun Profile(models: StateFlow) {


    val model by models.collectAsState()

    when(model) {

    ProfileModel.Loading
    -> ...




    ProfileModel.Success
    -> ...

    }



    }

    View Slide

  52. Setup
    View
    Presenter

    (Composable)

    View Slide

  53. Setup
    View View Model

    View Slide

  54. Resources
    ● Try out Molecule in Tivi App by Chris Banes

    View Slide

  55. View Model
    class ProfileViewModel: ViewModel() {


    private val moleculeScope = CoroutineScope(

    viewModelScope.coroutineContext +

    AndroidUiDispatcher.Main

    )

    }

    View Slide

  56. View Model
    class ProfileViewModel: ViewModel() {


    private val moleculeScope = CoroutineScope(

    viewModelScope.coroutineContext +

    AndroidUiDispatcher.Main

    )

    }

    View Slide

  57. View Model
    class ProfileViewModel: ViewModel() {


    val stateFlow: StateFlow

    }

    View Slide

  58. View Model
    class ProfileViewModel: ViewModel() {


    val stateFlow = moleculeScope.launchMolecule {

    }

    }

    View Slide

  59. View Model
    class ProfileViewModel: ViewModel() {


    val stateFlow = moleculeScope.launchMolecule {

    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    View Slide

  60. View Model
    class ProfileViewModel: ViewModel() {


    val stateFlow = moleculeScope.launchMolecule {

    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    View Slide

  61. Molecule
    ● How to setup presenter

    ● How to launch molecules

    View Slide

  62. Testing

    View Slide

  63. Testing
    val models: StateFlow = scope.launchMolecule {

    profilePresenter(postsFlow, likesFlow)

    }

    View Slide

  64. Test Cases
    Presenter Model

    View Slide

  65. Testing
    ● Uses Turbine

    ● Turbine is a library for testing flows

    View Slide

  66. Testing
    testImplementation "app.cash.molecule:molecule-testing"

    View Slide

  67. Presenter
    @Test

    fun `should get profiles`() {

    }

    View Slide

  68. Presenter
    @Test

    fun `should get profiles`() {

    val posts = MutableSharedFlow()

    val likes = MutableSharedFlow()

    }

    View Slide

  69. Presenter
    @Test

    fun `should get profiles`() {

    testMolecule({

    })

    }

    View Slide

  70. Presenter
    @Test

    fun `should get profiles`() {

    testMolecule({

    profilePresenter(posts, likes)

    })

    }

    View Slide

  71. Presenter
    @Test

    fun `should get profiles`() {

    testMolecule({

    profilePresenter(posts, likes)

    }) {

    assertEquals(

    ProfileModel.Loading, awaitItem()

    )

    }

    }

    View Slide

  72. Presenter
    @Test

    fun `should get profiles`() {

    testMolecule({

    profilePresenter(posts, likes)

    }) {

    posts.emit(data)

    }

    }

    View Slide

  73. Presenter
    @Test

    fun `should get profiles`() {

    testMolecule({

    profilePresenter(posts, likes)

    }) {

    posts.emit(data)

    assertEquals(

    ProfileModel.Success(data), awaitItem()

    )

    View Slide

  74. Testing
    ● Setup tests

    ● How to use Turbine

    View Slide

  75. Molecule Internals

    View Slide

  76. Internals
    ● Setup Recomposer

    ● Schedule recomposition

    ● Run composition on the passed in body

    View Slide

  77. Internals
    fun CoroutineScope.launchMolecule(

    emitter: (value: T)
    ->
    Unit,

    body: @Composable ()
    ->
    T,

    )

    View Slide

  78. Internals
    fun CoroutineScope.launchMolecule(

    emitter: (value: T)
    ->
    Unit,

    body: @Composable ()
    ->
    T,

    )

    View Slide

  79. Launching Molecule
    val models: StateFlow = scope.launchMolecule {

    profilePresenter(events)

    }

    View Slide

  80. Internals
    var flow: MutableStateFlow? = null

    View Slide

  81. Internals
    var flow: MutableStateFlow? = null

    launchMolecule(

    emitter = { value
    ->


    outputFlow.value = value

    },

    ...


    )

    View Slide

  82. Internals
    fun CoroutineScope.launchMolecule(

    emitter: (value: T)
    ->
    Unit,

    body: @Composable ()
    ->
    T,

    )

    View Slide

  83. Internals
    ● Setup Recomposer

    View Slide

  84. Internals
    launchMolecule() {

    val recomposer = Recomposer(coroutineContext)

    }
    Scheduler for performing
    recomposition

    View Slide

  85. Internals
    launchMolecule() {

    val recomposer = Recomposer(coroutineContext)

    val composition = Composition(recomposer)

    }
    Used to apply composition

    View Slide

  86. Internals
    ● Setup Recomposer

    ● Schedule recomposition

    View Slide

  87. Internals
    launchMolecule() {

    launch {

    recomposer.runRecomposeAndApplyChanges()

    }

    }

    Suspends


    - Await the invalidation of any

    associated composers.

    View Slide

  88. Internals
    launchMolecule() {

    Snapshot.registerGlobalWriteObserver {

    Snapshot.sendApplyNotifications()

    }

    }
    Register an observer

    Called when a state object is modified

    View Slide

  89. Internals
    ● Setup Recomposer

    ● Schedule recomposition

    ● Run composition on the passed in body

    View Slide

  90. Internals
    launchMolecule() {

    composition.setContent {

    emitter(body())

    }

    } Update composition

    View Slide

  91. Launching Molecule
    val models: StateFlow = scope.launchMolecule {

    profilePresenter(events)

    }

    View Slide

  92. Example App

    View Slide

  93. Android App Example
    ● How to use it in multiple presenters

    ● Launching Molecules

    View Slide

  94. View Slide

  95. Screens
    ● Displays scores

    ● Player info

    ● Weather info

    View Slide

  96. Multiplatform App
    Shared
    Android iOS
    GraphQL

    View Slide

  97. Architecture
    View Presenter
    Events

    View Slide

  98. Architecture
    View Presenter
    Model

    View Slide

  99. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  100. Questions
    ● How do you setup the Presenter with Dagger?

    View Slide

  101. Presenter
    class ScoresPresenter @Inject constructor() {

    }

    View Slide

  102. Presenter
    @Composable

    fun profilePresenter(postsFlow, likesFlow): ProfileModel {


    val posts by postsFlow.collectAsState(null)

    val likes by likesFlow.collectAsState(0L)

    return if (posts
    ==
    null) {

    ProfileModel.Loading

    } else {

    ProfileModel.Success(posts, likes)

    }

    }

    View Slide

  103. Questions
    ● How do you setup the composable?

    View Slide

  104. Presenter
    class ScoresPresenter @Inject constructor() {

    @Composable

    fun present(events): ScoresModel {

    }

    }

    View Slide

  105. Screens
    ● Displays scores

    ● Player info

    ● Weather info

    View Slide

  106. Presenter
    class PlayersPresenter @Inject constructor() {

    @Composable

    fun present(events): PlayersModel {

    }

    }

    View Slide

  107. Questions
    ● How do you create a pattern for setting up multiple


    presenter?

    View Slide

  108. Presenter
    interface MoleculePresenter {

    @Composable

    fun present(events: Flow): Model

    }

    View Slide

  109. Presenter
    class ScoresPresenter: MoleculePresenter {

    @Composable

    override fun present(events): ScoresModel {

    }

    }

    View Slide

  110. Questions
    ● How do you launch molecules for the presenter?

    View Slide

  111. Navigation
    Players
    Navigator
    Scores Weather

    View Slide

  112. Navigator
    @Composable

    fun AppNavigator(

    scoresModels: StateFlow,

    playersModels: StateFlow,

    weatherModels: StateFlow

    )

    View Slide

  113. Navigator
    class MainActivity : ComponentActivity() {

    override fun onCreate() {

    setContent {

    }

    }

    }

    View Slide

  114. Navigator
    class MainActivity : ComponentActivity() {

    override fun onCreate() {

    setContent {

    AppNavigator(
    ...
    )

    }

    }

    }

    View Slide

  115. class MainActivity : ComponentActivity() {

    val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow)

    }

    }
    Launching Molecules

    View Slide

  116. class MainActivity : ComponentActivity() {

    val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow)

    }

    val playerModels = scope.launchMolecule {

    playersPresenter.present(events = playersFlow)

    }

    }
    Launching Molecules

    View Slide

  117. Navigator
    class MainActivity : ComponentActivity() {

    override fun onCreate() {

    setContent {

    AppNavigator(

    playersModels,

    scoresModels

    )

    }

    }

    }

    View Slide

  118. Approach
    Players
    Main Activity
    Scores Weather

    View Slide

  119. Approach
    Players
    Main Activity
    Scores Weather Screen
    ……

    View Slide

  120. class MainActivity : ComponentActivity() {

    val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow)

    }

    }
    Launching Molecules

    View Slide

  121. Questions
    ● How do you setup launching molecules for many


    presenters?

    View Slide

  122. class MainActivity : ComponentActivity() {

    @Inject

    lateinit var scoresPresenter: ScoresPresenter

    @Inject

    lateinit var weatherPresenter: WeatherPresenter

    }
    Dependency Injection

    View Slide

  123. Questions
    ● How do you setup DI with single activity multiple


    composable?

    View Slide

  124. Feedback

    View Slide

  125. Feedback

    View Slide

  126. Feedback
    val scoresFlow = remember { simulator.simulate(speed = 1000) }

    val scoresModel = scoresPresenter.present(events = scoresFlow)

    ScoresScreen(

    tournamentInfoModel,

    scoresModel

    )

    View Slide

  127. Feedback
    val scoresFlow = remember { simulator.simulate(speed = 1000) }

    val scoresModel = scoresPresenter.present(events = scoresFlow)

    ScoresScreen(

    tournamentInfoModel,

    scoresModel

    )

    View Slide

  128. Feedback
    val scoresFlow = remember { simulator.simulate(speed = 1000) }

    val scoresModel = scoresPresenter.present(events = scoresFlow)

    ScoresScreen(

    scoresModel

    )

    View Slide

  129. Using Jetpack Compose with Square's Molecule Library
    ● How to setup and use Molecule

    ● Testing with Molecule

    ● Molecule Internals

    ● Example App

    View Slide

  130. Resources
    ● Using Jetpack Compose with Molecule Library

    ● Molecule Library

    View Slide

  131. Resources

    View Slide

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

    View Slide