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

Compose Basics for Android Devs

Avatar for Bruno Aybar Bruno Aybar
November 14, 2025
6

Compose Basics for Android Devs

Are you an experienced Android developer still managing XML layouts, Fragments, and RecyclerView Adapters ? It's time to "think in Compose". Jetpack Compose is a modern, 100% Kotlin-based toolkit that fundamentally changes how we build UIs .

This session is a practical, from-the-ground-up guide for developers with existing Android knowledge. We will abandon the old imperative model and learn to build UIs by describing what we want, not how to build it .

Avatar for Bruno Aybar

Bruno Aybar

November 14, 2025
Tweet

Transcript

  1. This work is licensed under the Apache 2.0 License Bruno

    Aybar Shopify, Senior Mobile Developer 
 
 Twitter: @brunoaybarg 
 Github: @Bruno125 Expositor
  2. This work is licensed under the Apache 2.0 License Requisitos:

    
 Conocimientos básicos de - Android - Kotlin
  3. This work is licensed under the Apache 2.0 License 2

    3 1 Compose Essentials Layouts, theming, 
 and animation Architecture and state (3 horas) (2 horas) (5 horas) Tus primeros pasos con Jetpack Compose. Entenderás lo que significa que Compose sea un toolkit de UI declarativo, y cómo usarlo para construir UIs increíbles. Jetpack Compose para Android Developers Este curso contiene 5 secciones:
  4. This work is licensed under the Apache 2.0 License 5

    4 Accessibility, testing, and performance Form factors (3 horas) (2 horas) Jetpack Compose para Android Developers
  5. This work is licensed under the Apache 2.0 License Jetpack

    Compose for Android Developers Course
  6. This work is licensed under the Apache 2.0 License •

    Pensando en “Compose" • Funciones Composable • Compose toolkit • Tooling (Android Studio) Agenda
  7. ItemsAdapter.kt class ItemsAdapter: RecyclerView.Adapter { fun onCreateViewHolder () { …

    } fun onBindViewHolder () { … } fun getItemCount () { … } class ItemsViewHolder: RecyclerView.VH() { ….. } } Cómo es hoy en día
  8. This work is licensed under the Apache 2.0 License Construye

    la interfaz describiendo el qué, no el cómo.
  9. @Composable fun ItemRow(item: Item) { Row { Image(imageResource(item.image)) Column {

    Text(item.title) Text(item.description) } } } Usando Compose
  10. @Composable fun ItemRow(item: Item) { Row { Image(imageResource(item.image)) Column {

    Text(item.title) Text(item.description) } } } Usando Compose
  11. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } } 1. Solución 100% orientada a Kotlin 

  12. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } } 1. Solución 100% orientada a Kotlin 
 2. UI declarativa, basada en @Composables
  13. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } } @Composables
  14. ¿Qué podemos ver aquí? @Composable fun ItemRow(item: Item) { Row

    { Image(imageResource(item.image)) Column { Text(item.title) Text(item.description) } } } 1. Solución 100% orientada a Kotlin 
 2. UI declarativa, basada en @Composables 
 3. Composición en lugar de Herencia
  15. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(selected = false, onClick = { /* … */ }) } } // SurveyAnswer.kt
  16. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(selected = false, onClick = { /* … */ }) } } // SurveyAnswer.kt
  17. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(false, onClick = { /* … */ }) } } // SurveyAnswer.kt
  18. This work is licensed under the Apache 2.0 License Construye

    la interfaz describiendo el qué, no el cómo.
  19. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { / * ... */ var selected: Boolean = // ... RadioButton(selected, onClick = { /* … */ }) } } // SurveyAnswer.kt Los Estados controlan la UI
  20. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { / * ... */ var selected: Boolean = // ... RadioButton(selected, onClick = { selected = !selected }) } } // SurveyAnswer.kt Los Eventos controlan el estado
  21. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { / * ... */ var selected: Boolean = // ... RadioButton(selected, onClick = { selected = !selected }) } } // SurveyAnswer.kt
  22. Describe el qué, no el cómo Elementos de UI =

    Funciones Los Estados controlan la UI Los Eventos controlan el State 1 2 3 4
  23. This work is licensed under the Apache 2.0 License //

    SurveyAnswer.kt @Composable fun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(false, onClick = { /* … */ }) } }
  24. This work is licensed under the Apache 2.0 License //

    SurveyAnswer.kt @Composable fun SurveyAnswer(answer: Answer) { /* … */ } @Composable fun SingleChoiceQuestion(answers: List<Answer>) { Column { answers.forEach { answer -> SurveyAnswer(answer = answer) } } }
  25. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { Column { answers.forEach { answer -> SurveyAnswer(answer = answer) } } }
  26. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { Column { answers.forEach { answer -> // No debemos hacer esto!! val answer = SurveyAnswer(answer = answer) } } }
  27. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { Column { if (answers.isEmpty()) { Text(“No hay opciones!”) } else { answers.forEach { answer -> SurveyAnswer(answer = answer) } } } }
  28. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { // Rápido y sin efectos secundarios Column { if (answers.isEmpty()) { /* ... */ } else { /* ... */ } } }
  29. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { // No debería haber efectos secundarios SurveyApp.didShowSingleChoiceQuestion = true Column { if (answers.isEmpty()) { /* ... */ } else { /* ... */ } } }
  30. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { Column { answers.forEach { answer -> SurveyAnswer(answer = answer) } } }
  31. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  32. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  33. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  34. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? = null answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  35. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? = null answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  36. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = mutableStateOf(null) answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  37. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = mutableStateOf(null) answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) } }
  38. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = mutableStateOf(null) answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.state == answer), ) } }
  39. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = remember { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.state == answer), ) } }
  40. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.state == answer), ) } }
  41. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? by rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.state == answer), ) } }
  42. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? by rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer == answer), ) } }
  43. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? by rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer == answer), onAnswerSelected = { answer -> selectedAnswer = answer } ) } }
  44. This work is licensed under the Apache 2.0 License Event

    handler UI element event state Events change State
  45. This work is licensed under the Apache 2.0 License Event

    handler UI element onAnswerSelected answer Events change State
  46. This work is licensed under the Apache 2.0 License //

    SingleChoiceQuestion.kt @Composable fun SingleChoiceQuestion(answers: List<Answer>) { val selectedAnswer: Answer? by rememberSaveable { mutableStateOf<Answer>(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer == answer), onAnswerSelected = { answer -> selectedAnswer = answer } ) } }
  47. This work is licensed under the Apache 2.0 License Las

    funciones Composable se pueden ejecutar en cualquier orden.
  48. This work is licensed under the Apache 2.0 License //

    ButtonRow.kt @Composable fun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() } }
  49. This work is licensed under the Apache 2.0 License Las

    funciones Composable pueden correr en paralelo.
  50. This work is licensed under the Apache 2.0 License @Composable

    fun ListComposable(myList: List<String>) { Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item") } } Text("Count: ${myList.size}") } } // ListComposable.kt
  51. This work is licensed under the Apache 2.0 License @Composable

    fun ListWithBug(myList: List<String>) { var items = 0 Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item”) items++ // Evitar! Efecto secundario de la recomposición de la columna } } Text("Count: $items") } } // ListComposable.kt
  52. This work is licensed under the Apache 2.0 License La

    Recomposición evita la mayor cantidad de pasos posible.
  53. This work is licensed under the Apache 2.0 License @Composable

    fun GreetingScreen(name: String) { Column { Header() Greeting(name = name) Footer() } } // GreenScreen.kt
  54. This work is licensed under the Apache 2.0 License Las

    funciones Composable pueden ejecutarse de forma seguida.
  55. This work is licensed under the Apache 2.0 License ¿Cómo

    usarlos? Se usa la anotación @Composable Aceptan parámetros Usa MutableState y remember No deben tener efectos secundarios 1 2 3 4
  56. This work is licensed under the Apache 2.0 License ¿Cómo

    se ejecutan? Pueden… Ejecutarse en cualquier orden Correr en paralelo Ser “salteadas" / "ignoradas" Correr frecuentemente 1 2 3 4
  57. This work is licensed under the Apache 2.0 License MaterialTheme(

    colorScheme = MyAppsColorScheme, typography = MyAppsTypography, shapes = MyAppsShapes ) { // Content goes here }
  58. This work is licensed under the Apache 2.0 License Scaffold(

    topBar = { SmallTopAppBar(/* ... */) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingActionButton(/* ... */) }, content = { /* ... */ } )
  59. This work is licensed under the Apache 2.0 License Scaffold(

    topBar = { SmallTopAppBar(/* ... */) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingActionButton(/* ... */) }, content = { /* ... */ } )
  60. This work is licensed under the Apache 2.0 License Scaffold(

    topBar = { SmallTopAppBar(/* ... */) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingActionButton(/* ... */) }, content = { /* ... */ } )
  61. This work is licensed under the Apache 2.0 License Scaffold(

    topBar = { SmallTopAppBar(/* ... */) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingActionButton(/* ... */) }, content = { /* ... */ } )
  62. This work is licensed under the Apache 2.0 License Surface

    { Text("Hello Compose") } Hello Compose
  63. This work is licensed under the Apache 2.0 License Surface(

    color = MaterialTheme.colorScheme.primary, ) { Text("Hello Compose") } Hello Compose
  64. This work is licensed under the Apache 2.0 License Surface(

    color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(8.dp), ) { Text("Hello Compose") } Hello Compose
  65. This work is licensed under the Apache 2.0 License Surface(

    color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, MaterialTheme.colorScheme.outline ) ) { Text("Hello Compose") } Hello Compose
  66. This work is licensed under the Apache 2.0 License Surface(

    color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, MaterialTheme.colorScheme.surfaceVariant ), shadowElevation = 8.dp, tonalElevation = 8.dp, ) { Text("Hello Compose") } Hello Compose
  67. This work is licensed under the Apache 2.0 License Row

    { Component1() Component2() Component3() } 1 2 3 Row
  68. This work is licensed under the Apache 2.0 License 1

    2 3 Column Column { Component1() Component2() Component3() }
  69. This work is licensed under the Apache 2.0 License 2

    1 3 Box Box { Component1() Component2() Component3() }
  70. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(/* … */) } }
  71. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row( verticalAlignment = Alignment.CenterVertically ) { /* ... */ } }
  72. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { /* ... */ } }
  73. This work is licensed under the Apache 2.0 License Text(

    "Hello Compose!", Modifier.background(Color.Magenta) ) Hello Compose
  74. This work is licensed under the Apache 2.0 License Text(

    "Hello Compose!", Modifier.background(Color.Magenta) .size(200.dp, 30.dp) ) Hello Compose
  75. This work is licensed under the Apache 2.0 License Text(

    "Hello Compose!", Modifier.background(Color.Magenta) .size(200.dp, 30.dp) .padding(5.dp) ) Hello Compose
  76. This work is licensed under the Apache 2.0 License Text(

    "Hello Compose!", Modifier.background(Color.Magenta) .size(200.dp, 30.dp) .padding(5.dp) .alpha(0.5f) ) Hello Compose
  77. This work is licensed under the Apache 2.0 License Text(

    "Hello Compose!", Modifier.background(Color.Magenta) .size(200.dp, 30.dp) .padding(5.dp) .alpha(0.5f) .clickable { // Called when Text clicked } ) Hello Compose
  78. This work is licensed under the Apache 2.0 License Box(Modifier.size(150.dp))

    { Text( "Hello Compose!", Modifier.align( Alignment.BottomEnd ) ) } Hello Compose
  79. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row(...) { Image(answer.image) Text(answer.text) RadioButton(/* … */) } } Desired Current
  80. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row( Modifier.fillMaxWidth(), /* ... */ ) { Image(answer.image) Text(answer.text) RadioButton(/* ... */) } } Desired Current
  81. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Row( Modifier.fillMaxWidth() .padding(16.dp), /* ... */ ) { Image(answer.image) Text(answer.text) RadioButton(/* ... */) } } Desired Current
  82. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Surface( border = BorderStroke( 1.dp, MaterialTheme.colorScheme.outline ), shape = MaterialTheme.shapes.small ) { Row(/* ... */) { } } } Desired Current
  83. This work is licensed under the Apache 2.0 License @Composable

    fun SurveyAnswer(answer: Answer) { Surface( border = BorderStroke( 1.dp, MaterialTheme.colorScheme.outline ), shape = MaterialTheme.shapes.small ) { Row(Modifier.fillMaxWidth().padding(16.dp)) { Image(answer.image) Text(answer.text) RadioButton(/* … */) } } }