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

Mastering Recompositions in Compose

Mastering Recompositions in Compose

One of the greatest fears we face when we start with Compose is the difficulty of understanding how the recomposition system works: when Compose repaints the screen.

In general, it is an unfounded fear, as the system works very well on its own, but there are situations where we may encounter serious performance issues.

In this talk, we will see:

- What a recomposition really is
- How the recomposition system works
- How to detect problems
- What strategies to follow to solve those problems

Antonio Leiva

May 21, 2024
Tweet

More Decks by Antonio Leiva

Other Decks in Technology

Transcript

  1. Mastering Recompositions in Compose 1. ­ How Compose works 2.

    ­ Recomposition 3. ­ Debugging 4. ­ Problems and solutions
  2. buttonEnabled = false UiState( 1 user = "user", 2 pass

    = "pass", 3 4 ) 5 user pass CAN'T CLICK
  3. How Jetpack Compose works ­ Specific implementation of interface declarative

    paradigm ­ Widgets in screen are functions ­ Widgets are stateless
  4. How Jetpack Compose works ­ Specific implementation of interface declarative

    paradigm ­ Widgets in screen are functions ­ Widgets are stateless ­ State provided by function arguments
  5. @Composable fun MoviesList( val title: String, val movies: List<Movie> )

    { // Implementation } When state changes, UI is redrawn Otherwise, redrawing should be skipped
  6. When is the state changed? ­ It's hard to reason

    about it ­ Algorithm is confusing
  7. When is the state changed? ­ It's hard to reason

    about it ­ Algorithm is confusing ­ It sometimes go against logic
  8. When is the state changed? ­ It's hard to reason

    about it ­ Algorithm is confusing ­ It sometimes go against logic What does it mean that state is changed?
  9. When is a state stable? ­ All primitive types: Boolean,

    Int, Long, Float, Char... ­ Strings
  10. When is a state stable? ­ All primitive types: Boolean,

    Int, Long, Float, Char... ­ Strings ­ All functional types (lambdas)
  11. When is a state stable? ­ All primitive types: Boolean,

    Int, Long, Float, Char... ­ Strings ­ All functional types (lambdas) ­ Immutable classes using previous types
  12. A stable class data class Movie( val id: Int, val

    title: String, val description: String )
  13. When is a state unstable? Almost any other case: ­

    Collections are not stable ­ Mutable classes
  14. When is a state unstable? Almost any other case: ­

    Collections are not stable ­ Mutable classes ­ All classes from modules where Compose compiler is not run
  15. An unstable class data class Movie( val id: Int, val

    title: String, var description: String )
  16. An unstable class var description: String data class Movie( 1

    val id: Int, 2 val title: String, 3 4 ) 5
  17. An unstable Composable val movies: List<Movie> @Composable 1 fun MoviesList(

    2 val title: String, 3 4 ) { 5 // Implementation 6 } 7
  18. Stability reports - Configuration kotlinOptions { val composeReportsDir = "compose_reports"

    freeCompilerArgs += listOf( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.layout.buildDirectory.get().dir(composeReportsDir).asFile.absolutePath, "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.layout.buildDirectory.get().dir(composeReportsDir).asFile.absolutePath, ) }
  19. Classes report Stable class stable class Movie { stable val

    id: Int stable val title: String stable val overview: String stable val releaseDate: String stable val poster: String stable val backdrop: String? stable val originalTitle: String stable val originalLanguage: String stable val popularity: Double stable val voteAverage: Double stable val isFavorite: Boolean <runtime stability> = Stable }
  20. Classes report Unstable class unstable class UiState { stable val

    loading: Boolean unstable val movies: List<Movie> <runtime stability> = Unstable }
  21. 4. Problems and Solutions 1. Unstable class 2. External unstable

    class 3. Lambdas 4. LazyList 5. Columns 6. State continuously changing 7. Modifiers
  22. 2. External unstable class Collections, Other modules, 3rd party libraries

    Option 1: Stable wrapper @Stable class ListWrapper { val movies: List<Movie> }
  23. 2. External unstable class Collections, Other modules, 3rd party libraries

    Option 2 (recommended): Stability config file // stability-config.txt kotlin.collections.* kotlin.Throwable
  24. 3. Lambdas @Composable fun MoviesScreen() { val viewModel = viewModel

    { MoviesViewModel() } val state by viewModel.state.collectAsState() MoviesListWithButton( movies = state.movies, onButtonClick = { viewModel.onAddMovie() }, onMovieClick = { viewModel.onMovieClick() }, ) }
  25. 3. Lambdas val viewModel = viewModel { MoviesViewModel() } onButtonClick

    = { viewModel.onAddMovie() }, onMovieClick = { viewModel.onMovieClick() }, @Composable 1 fun MoviesScreen() { 2 3 val state by viewModel.state.collectAsState() 4 5 MoviesListWithButton( 6 movies = state.movies, 7 8 9 ) 10 } 11
  26. 3. Lambdas Option 1: function reference val viewModel = viewModel

    { MoviesViewModel() } onButtonClick = viewModel::onAddMovie, onMovieClick = viewModel::onMovieClick, @Composable 1 fun MoviesScreen() { 2 3 val state by viewModel.state.collectAsState() 4 5 MoviesListWithButton( 6 movies = state.movies, 7 8 9 ) 10 } 11
  27. 3. Lambdas Option 2: Manual memoization val onAddMovie = remember(viewModel)

    {{ viewModel.onAddMovie() }} val onMovieClick = remember(viewModel) {{ viewModel.onMovieClick() }} onButtonClick = onAddMovie, onMovieClick = onMovieClick, 1 2 3 MoviesListWithButton( 4 movies = state.movies, 5 6 7 ) 8
  28. 4. LazyColumn items(movies, key = { it.id }) { LazyColumn

    { 1 2 MovieItem(movie = it) 3 } 4 } 5
  29. 5. Column key(movie.id) { } Column { 1 for (movie

    in movies) { 2 3 MovieItem(movie) 4 5 } 6 } 7
  30. 6. State continously changing val showScrollToTop = lazyState.firstVisibleItemIndex > 0

    👇 val showScrollToTop by remember { derivedStateOf { lazyState.firstVisibleItemIndex > 0 } }
  31. 7. Defer reads from composition to layout var sliderValue by

    remember { mutableFloatStateOf(0f) } Column { Slider(value = sliderValue, onValueChange = { sliderValue = it }) RedBall(offset = sliderValue * 200) }
  32. 7. Defer reads from composition to layout RedBall(offset = sliderValue

    * 200) var sliderValue by remember { mutableFloatStateOf(0f) } 1 Column { 2 Slider(value = sliderValue, onValueChange = { sliderValue = it }) 3 4 } 5
  33. 7. Defer reads from composition to layout fun RedBall(offset: Float)

    { .offset(offset.dp) @Composable 1 2 Box( 3 modifier = Modifier 4 5 .size(56.dp) 6 ) 7 } 8
  34. 7. Defer reads from composition to layout fun RedBall(offset: ()

    -> Float) { .offset { IntOffset(offset().toInt(), 0) } @Composable 1 2 Box( 3 modifier = Modifier 4 5 .size(56.dp) 6 ) 7 } 8
  35. Strong Skipping Mode Skippable Composables with unstable parameters Unstable parameters

    👉 Compared by reference (===) Stable parameters 👉 Compared by equals() (==) All lambdas are memoized (remember) Even in unstable scopes
  36. TLDR; Don't do premature optimization If there's a performance problem,

    debug: Layout inspector Stability report Future: use new recomposition system
  37. Bibliography (1/2) 📄 (Android Developers) 📄 (Android Developers) 📄 (Android

    Developers) 📄 (Phat Nguyen) 📄 (Vinay Gaba) 📄 (Justin Breitfeller) 📄 (Ben Trengrove) 📄 (Francesc Vilarino) Thinking in Compose Lifecycle of Compose Jetpack Compose Performance Debugging the recomposition in Jetpack Compose What is “donut-hole skipping” in Jetpack Compose? Gotchas in Jetpack Compose Recomposition Jetpack Compose: Strong Skipping Mode Explained Exploring Jetpack Compose Compiler’s Stability Config
  38. Bibliography (2/2) 📄 (Ben Trengrove) 💬 (Leland Richardson) 🎥 (Android

    Developers 🎥 (Android Developers) 🎥 (Philipp Lackner) 🎥 (Philipp Lackner) Jetpack Compose Stability Explained Reddit answer about unstable lambdas Jetpack Compose: Debugging recomposition From data to UI: Compose phases - MAD Skills Performance Optimization with @Stable and @Immutable in Jetpack Compose I Bet You DON'T Know These 3 Performance Optimizations for your Jetpack Compose UI