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

  2. Using Jetpack Compose with Square's Molecule Library • How to

    setup and use Molecule • Testing with Molecule • Molecule Internals • Example App
  3. Molecule Problems, Purpose

  4. Problem

  5. Problem Flow 1

  6. Problem Flow 1 Flow 2

  7. Problem @Composable 
 fun Profile(flow1, flow2) { val state1 by

    flow1.collectAsState(null) val state2 by flow2.collectAsState(0L) ... }
  8. Problem @Composable 
 fun Profile(viewModel: ProfilesViewModel) { val state1 by

    flow1.collectAsState(null) val state2 by flow2.collectAsState(0L) ... } 
 Mixes business logic with view
  9. Approach Presenter 
 (Composable)

  10. Approach Events Presenter 
 (Composable)

  11. Approach Events Presenter 
 (Composable) Molecule

  12. Approach Events Presenter 
 (Composable) Molecule

  13. Approach Events Presenter 
 (Composable) Molecule State Flow

  14. Molecule Purpose 
 Build a StateFlow using Jetpack Compose

  15. Compose vs Compose UI

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

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

  18. Compose • General purpose tool for managing tree of nodes.

    Node can be anything
  19. Compose UI • UI Toolkit

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

  21. Resources • A Jetpack Compose by any other name -

    Jake Wharton • Compose From First Principles - Leland Richardson
  22. Molecule • Kotlin Compiler Plugin • Continually recomposes based on

    clock that controls 
 
 when recomposition occurs
  23. How to use Molecule Setup & Launch molecules

  24. Problem Flow 1 Flow 2

  25. Setup View

  26. Setup View Molecule

  27. Setup View Molecule Presenter 
 (Composable) State

  28. Setup View Molecule Presenter 
 (Composable) Events

  29. Presenter @Composable 
 fun profilePresenter( ... ) { }

  30. Model sealed class ProfilesModel { object Loading: ProfilesModel() data class

    Data( ... ): ProfilesModel() }
  31. Presenter @Composable 
 fun profilePresenter( ... ): ProfileModel { }

  32. Problem Flow 1 Flow 2

  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  39. Setup Molecule Presenter 
 (Composable)

  40. Setup Molecule Presenter 
 (Composable) StateFlow

  41. Launching Molecule • Define a coroutine scope

  42. Launching Molecule val scope = CoroutineScope(Dispatchers.Main) val models: StateFlow<UsersModel> =

    scope.launchMolecule { userPresenter(postsFlow, likesFlow) }
  43. Launching Molecule • Define a coroutine scope • Specify frame

    clock
  44. Monotomic Frame Clock • Controls when recomposition occurs • AndroidUiDispatcher

    • BroadcastFrameClock
  45. Launching Molecule val scope = CoroutineScope( Dispatchers.Main + AndroidUiDispatcher.Main )

  46. Launching Molecule • Define a coroutine scope • Specify frame

    clock • Use launchMolecule method to return state from presenter
  47. Launching Molecule val models: StateFlow<ProfileModel> = scope.launchMolecule { profilePresenter(postsFlow, likesFlow)

    }
  48. Setup View Molecule

  49. View @Composable 
 fun Profile(models: StateFlow<ProfileModel>) { 
 
 val

    model by models.collectAsState() when(model) { UsersModel.Loading -> ... UsersModel.Success -> ...
 } }
  50. View @Composable 
 fun Profile(models: StateFlow<ProfileModel>) { 
 
 val

    model by models.collectAsState() when(model) { UsersModel.Loading -> ... UsersModel.Success -> ...
 } }
  51. View @Composable 
 fun Profile(models: StateFlow<ProfileModel>) { 
 
 val

    model by models.collectAsState() when(model) { ProfileModel.Loading -> ... ProfileModel.Success -> ...
 } }
  52. Setup View Presenter 
 (Composable)

  53. Setup View View Model

  54. Resources • Try out Molecule in Tivi App by Chris

    Banes
  55. View Model class ProfileViewModel: ViewModel() { 
 private val moleculeScope

    = CoroutineScope( viewModelScope.coroutineContext + AndroidUiDispatcher.Main ) }
  56. View Model class ProfileViewModel: ViewModel() { 
 private val moleculeScope

    = CoroutineScope( viewModelScope.coroutineContext + AndroidUiDispatcher.Main ) }
  57. View Model class ProfileViewModel: ViewModel() { 
 val stateFlow: StateFlow<ProfileModel>

    }
  58. View Model class ProfileViewModel: ViewModel() { 
 val stateFlow =

    moleculeScope.launchMolecule { } }
  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) }
  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) }
  61. Molecule • How to setup presenter • How to launch

    molecules 

  62. Testing

  63. Testing val models: StateFlow<ProfileModel> = scope.launchMolecule { profilePresenter(postsFlow, likesFlow) }

  64. Test Cases Presenter Model

  65. Testing • Uses Turbine • Turbine is a library for

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

  67. Presenter @Test fun `should get profiles`() { }

  68. Presenter @Test fun `should get profiles`() { val posts =

    MutableSharedFlow<Posts>() val likes = MutableSharedFlow<Likes>() }
  69. Presenter @Test fun `should get profiles`() { testMolecule({ }) }

  70. Presenter @Test fun `should get profiles`() { testMolecule({ profilePresenter(posts, likes)

    }) }
  71. Presenter @Test fun `should get profiles`() { testMolecule({ profilePresenter(posts, likes)

    }) { assertEquals( ProfileModel.Loading, awaitItem() ) } }
  72. Presenter @Test fun `should get profiles`() { testMolecule({ profilePresenter(posts, likes)

    }) { posts.emit(data) } }
  73. Presenter @Test fun `should get profiles`() { testMolecule({ profilePresenter(posts, likes)

    }) { posts.emit(data) 
 assertEquals( ProfileModel.Success(data), awaitItem() )
  74. Testing • Setup tests • How to use Turbine 


  75. Molecule Internals

  76. Internals • Setup Recomposer • Schedule recomposition • Run composition

    on the passed in body
  77. Internals fun <T> CoroutineScope.launchMolecule( emitter: (value: T) -> Unit, body:

    @Composable () -> T, )
  78. Internals fun <T> CoroutineScope.launchMolecule( emitter: (value: T) -> Unit, body:

    @Composable () -> T, )
  79. Launching Molecule val models: StateFlow<UsersModel> = scope.launchMolecule { profilePresenter(events) }

  80. Internals var flow: MutableStateFlow<T>? = null

  81. Internals var flow: MutableStateFlow<T>? = null launchMolecule( emitter = {

    value -> outputFlow.value = value }, ... )
  82. Internals fun <T> CoroutineScope.launchMolecule( emitter: (value: T) -> Unit, body:

    @Composable () -> T, )
  83. Internals • Setup Recomposer

  84. Internals launchMolecule() { val recomposer = Recomposer(coroutineContext) } Scheduler for

    performing recomposition
  85. Internals launchMolecule() { val recomposer = Recomposer(coroutineContext) val composition =

    Composition(recomposer) } Used to apply composition
  86. Internals • Setup Recomposer • Schedule recomposition

  87. Internals launchMolecule() { launch { recomposer.runRecomposeAndApplyChanges() } } 
 Suspends

    
 
 - Await the invalidation of any associated composers.
  88. Internals launchMolecule() { Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() } } Register an

    observer 
 Called when a state object is modified
  89. Internals • Setup Recomposer • Schedule recomposition • Run composition

    on the passed in body
  90. Internals launchMolecule() { composition.setContent { emitter(body()) } } Update composition

  91. Launching Molecule val models: StateFlow<UsersModel> = scope.launchMolecule { profilePresenter(events) }

  92. Example App

  93. Android App Example • How to use it in multiple

    presenters • Launching Molecules
  94. None
  95. Screens • Displays scores • Player info • Weather info

  96. Multiplatform App Shared Android iOS GraphQL

  97. Architecture View Presenter Events

  98. Architecture View Presenter Model

  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) } }
  100. Questions • How do you setup the Presenter with Dagger?

  101. Presenter class ScoresPresenter @Inject constructor() { }

  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) } }
  103. Questions • How do you setup the composable?

  104. Presenter class ScoresPresenter @Inject constructor() { @Composable fun present(events): ScoresModel

    { } }
  105. Screens • Displays scores • Player info • Weather info

  106. Presenter class PlayersPresenter @Inject constructor() { @Composable fun present(events): PlayersModel

    { } }
  107. Questions • How do you create a pattern for setting

    up multiple 
 
 presenter?
  108. Presenter interface MoleculePresenter<Event, Model> { @Composable fun present(events: Flow<Event>): Model

    }
  109. Presenter class ScoresPresenter: MoleculePresenter<Event, ScoresModel> { @Composable override fun present(events):

    ScoresModel { } }
  110. Questions • How do you launch molecules for the presenter?

  111. Navigation Players Navigator Scores Weather

  112. Navigator @Composable fun AppNavigator( scoresModels: StateFlow<ScoreModel>, playersModels: StateFlow<PlayerModel>, weatherModels: StateFlow<WeatherModel>

    )
  113. Navigator class MainActivity : ComponentActivity() { override fun onCreate() {

    setContent { } } }
  114. Navigator class MainActivity : ComponentActivity() { override fun onCreate() {

    setContent { AppNavigator( ... ) } } }
  115. class MainActivity : ComponentActivity() { val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow) } } Launching Molecules
  116. class MainActivity : ComponentActivity() { val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow) } val playerModels = scope.launchMolecule { playersPresenter.present(events = playersFlow) } } Launching Molecules
  117. Navigator class MainActivity : ComponentActivity() { override fun onCreate() {

    setContent { AppNavigator( playersModels, scoresModels ) } } }
  118. Approach Players Main Activity Scores Weather

  119. Approach Players Main Activity Scores Weather Screen ……

  120. class MainActivity : ComponentActivity() { val scoresModels = scope.launchMolecule {

    scoresPresenter.present(events = scoresFlow) } } Launching Molecules
  121. Questions • How do you setup launching molecules for many

    
 presenters?
  122. class MainActivity : ComponentActivity() { @Inject lateinit var scoresPresenter: ScoresPresenter

    @Inject lateinit var weatherPresenter: WeatherPresenter } Dependency Injection
  123. Questions • How do you setup DI with single activity

    multiple 
 
 composable?
  124. Feedback

  125. Feedback

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

    val scoresModel = scoresPresenter.present(events = scoresFlow) ScoresScreen( tournamentInfoModel, scoresModel )
  127. Feedback val scoresFlow = remember { simulator.simulate(speed = 1000) }

    val scoresModel = scoresPresenter.present(events = scoresFlow) ScoresScreen( tournamentInfoModel, scoresModel )
  128. Feedback val scoresFlow = remember { simulator.simulate(speed = 1000) }

    val scoresModel = scoresPresenter.present(events = scoresFlow) ScoresScreen( scoresModel )
  129. Using Jetpack Compose with Square's Molecule Library • How to

    setup and use Molecule • Testing with Molecule • Molecule Internals • Example App
  130. Resources • Using Jetpack Compose with Molecule Library • Molecule

    Library
  131. Resources

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