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. Using Jetpack Compose with Square's Molecule Library • How to

    setup and use Molecule • Testing with Molecule • Molecule Internals • Example App
  2. Problem @Composable 
 fun Profile(flow1, flow2) { val state1 by

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

    flow1.collectAsState(null) val state2 by flow2.collectAsState(0L) ... } 
 Mixes business logic with view
  4. Resources • A Jetpack Compose by any other name -

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

    clock that controls 
 
 when recomposition occurs
  6. 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) } }
  7. 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) } }
  8. 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) } }
  9. 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) } }
  10. 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) } }
  11. 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) } }
  12. Launching Molecule • Define a coroutine scope • Specify frame

    clock • Use launchMolecule method to return state from presenter
  13. View @Composable 
 fun Profile(models: StateFlow<ProfileModel>) { 
 
 val

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

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

    model by models.collectAsState() when(model) { ProfileModel.Loading -> ... ProfileModel.Success -> ...
 } }
  16. View Model class ProfileViewModel: ViewModel() { 
 private val moleculeScope

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

    = CoroutineScope( viewModelScope.coroutineContext + AndroidUiDispatcher.Main ) }
  18. 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) }
  19. 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) }
  20. Presenter @Test fun `should get profiles`() { val posts =

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

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

    }) { posts.emit(data) 
 assertEquals( ProfileModel.Success(data), awaitItem() )
  23. Internals launchMolecule() { launch { recomposer.runRecomposeAndApplyChanges() } } 
 Suspends

    
 
 - Await the invalidation of any associated composers.
  24. Android App Example • How to use it in multiple

    presenters • Launching Molecules
  25. 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) } }
  26. 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) } }
  27. class MainActivity : ComponentActivity() { val scoresModels = scope.launchMolecule {

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

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

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

    scoresPresenter.present(events = scoresFlow) } } Launching Molecules
  31. class MainActivity : ComponentActivity() { @Inject lateinit var scoresPresenter: ScoresPresenter

    @Inject lateinit var weatherPresenter: WeatherPresenter } Dependency Injection
  32. Feedback val scoresFlow = remember { simulator.simulate(speed = 1000) }

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

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

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

    setup and use Molecule • Testing with Molecule • Molecule Internals • Example App