360|AnDev 2020: Learning Jetpack Compose By Example

360|AnDev 2020: Learning Jetpack Compose By Example

Over the course of the last few years Android development has gone through significant changes in how we structure our apps, the language we use for development, the tooling & libraries that help us speed up our development and the improvements in testing our apps. What had not changed in all these years is the Android UI toolkit. This changes with Jetpack Compose that aims to reimagine what Android UI development would look like using declarative programming principles. It is heavily influenced by existing web and mobile frameworks such as React, Litho, Vue & Flutter and would be a paradigm shift in Android UI development as we know it.

In this talk, we will take a deeper look at what declarative programming means and how we should think about it when building our apps. We will look into the main principles of Jetpack Compose and try to draw parallels with the “old Android way” of doing common tasks. Lastly, we will dive into various code examples and learn how to build layouts, manage state, write custom views, style our views, access resources and more, all using Jetpack Compose.

Companion code – https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example

E8da8d13d06ca69dbe019ecad71ed2a4?s=128

vinaygaba

July 23, 2020
Tweet

Transcript

  1. None
  2. Why do we need Jetpack Compose?

  3. Why do I need to learn new things? UI Toolkit

    is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code
  4. Why do I need to learn new things? UI Toolkit

    is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code
  5. Why do I need to learn new things? UI Toolkit

    is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code
  6. Why do I need to learn new things? UI Toolkit

    is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code
  7. Why do I need to learn new things? UI Toolkit

    is tied to the OS State Management is tricky Lots of context switching Simple things still require a lot of code
  8. Examples

  9. Disclaimer Examples are based on 0.1.0-dev14 version Compose is still

    pre-alpha so things are constantly changing and will continue to do so Alpha release is expected in the next few months
  10. Disclaimer Examples are based on 0.1.0-dev14 version Compose is still

    pre-alpha so things are constantly changing and will continue to do so Alpha release is expected in the next few months
  11. Disclaimer Examples are based on 0.1.0-dev14 version Compose is still

    pre-alpha so things are constantly changing and will continue to do so Alpha release is expected in the next few months
  12. Disclaimer Examples are based on 0.1.0-dev14 version Compose is still

    pre-alpha so things are constantly changing and will continue to do so Alpha release is expected in the next few months
  13. Example: Hello World

  14. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    } }
  15. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { } } }
  16. fun ComponentActivity.setContent( recomposer: Recomposer = Recomposer.current(), content: @Composable () ->

    Unit ): Composition { // Some magic ✨ }
  17. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { } } }
  18. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { Text(text = "Hello World") } } }
  19. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { Text(text = "Hello World") } } }
  20. class HelloWorldActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { Text(text = "Hello World") } } }
  21. @Composable fun CustomTextComponent() { Text(text = "Hello World") }

  22. @Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style

    = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) }
  23. @Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style

    = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) }
  24. @Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style

    = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) } @Preview @Composable fun CustomTextComponentPreview() { CustomTextComponent("Hello World") }
  25. None
  26. None
  27. Example: Show an Image

  28. @Composable fun DrawableImage() { }

  29. @Composable fun DrawableImage() { val image = loadImageResource(R.drawable.lena) }

  30. @Composable fun DrawableImage() { val image = loadImageResource(R.drawable.lena) image.resource.resource?.let {

    Image(asset = it, modifier = Modifier.preferredSize(200.dp)) } }
  31. @Composable fun DrawableImage(@DrawableRes resId: Int) { val image = loadImageResource(resId)

    image.resource.resource?.let { Image(asset = it, modifier = Modifier.preferredSize(200.dp)) } }
  32. @Composable fun DrawableImage(@DrawableRes resId: Int) { val image = loadImageResource(resId)

    image.resource.resource?.let { Image(asset = it, modifier = Modifier.preferredSize(200.dp)) } }
  33. Example: Alert Dialog

  34. // Classic Android AlertDialog.Builder(context) .setTitle("360|AnDev") .setMessage("Isn't this conference amazing?") .setPositiveButton(android.R.string.yes,

    null) .setNegativeButton(android.R.string.no, null) .setIcon(android.R.drawable.ic_dialog_alert) .show()
  35. @Composable fun AlertDialogComponent() { }

  36. @Composable fun AlertDialogComponent() { var showPopup by state { false

    } }
  37. @Composable fun AlertDialogComponent() { var showPopup by state { false

    } Button(onClick = { showPopup = true }, backgroundColor = Color.DarkGray) { Text(text = "Click Me") } }
  38. @Composable fun AlertDialogComponent() { var showPopup by state { false

    } Button(onClick = { showPopup = true }, backgroundColor = Color.DarkGray) { Text(text = "Click Me") } if (showPopup) { AlertDialog( onCloseRequest = { showPopup = false }, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }
  39. @Composable fun AlertDialogComponent() { var showPopup by state { false

    } Button(onClick = { showPopup = true }, backgroundColor = Color.DarkGray) { Text(text = "Click Me") } if (showPopup) { AlertDialog( onCloseRequest = { showPopup = false }, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }
  40. @Composable fun AlertDialogComponent() { var showPopup by remember { mutableStateOf(false)

    } val onButtonClicked = { showPopup = true } val onPopupDismissed = { showPopup = false } if (!showPopup) { Button(onClick = onButtonClicked, backgroundColor = Color.DarkGray) { Text(text = "Click Me") } } else { AlertDialog( onCloseRequest = onPopupDismissed, text = { Text("Congratulations! You just clicked the text successfully") }, confirmButton = { Button( onClick = onPopupDismissed ) { Text(text = "Ok") } } ) } }
  41. Example: Simple Layouts

  42. None
  43. Row

  44. Row 1 2

  45. 1 Column 1 Row 2

  46. @Composable fun ImageWithTitleSubtitleComponent() { }

  47. @Composable fun ImageWithTitleSubtitleComponent() { Row() { Column() { } }

    }
  48. @Composable fun ImageWithTitleSubtitleComponent() { Row(modifier = Modifier.fillMaxWidth() + Modifier.padding(16.dp)) {

    Column(modifier = Modifier.padding(start = 16.dp)) { } } }
  49. @Composable fun ImageWithTitleSubtitleComponent() { Row(modifier = Modifier.fillMaxWidth() + Modifier.padding(16.dp)) {

    DrawableImage(R.drawable.lenna) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = "Title") CustomTextComponent(displayText = "Subtitle") } } }
  50. @Composable fun ImageWithTitleSubtitleComponent(title: String, subtitle: String, imageUrl: String) { Row(modifier

    = Modifier.fillMaxWidth() + Modifier.padding(16.dp)) { NetworkImage(imageUrl) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = title) CustomTextComponent(displayText = subtitle) } } }
  51. @Composable fun ImageWithTitleSubtitleComponent(title: String, subtitle: String, imageUrl: String) { Row(modifier

    = Modifier.fillMaxWidth() + Modifier.padding(16.dp)) { NetworkImage(imageUrl) Column(modifier = Modifier.padding(start = 16.dp)) { CustomTextComponent(displayText = title) CustomTextComponent(displayText = subtitle) } } }
  52. Example: ConstraintLayout

  53. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { }

  54. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { ConstraintLayout

    { val (title, subtitle, image) = createRefs() } }
  55. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { ConstraintLayout

    { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, ) CustomTextComponent( displayText = subtitleText, ) NetworkImage( url = imageUrl, ) } }
  56. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { ConstraintLayout

    { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, modifier = Modifier.constrainAs(title) { start.linkTo(image.end, margin = 8.dp) top.linkTo(image.top) } ) CustomTextComponent( displayText = subtitleText, ) NetworkImage( url = imageUrl, ) } }
  57. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { ConstraintLayout

    { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, modifier = Modifier.constrainAs(title) { start.linkTo(image.end, margin = 8.dp) top.linkTo(image.top) } ) CustomTextComponent( displayText = subtitleText, modifier = Modifier.constrainAs(subtitle) { bottom.linkTo(image.bottom) start.linkTo(image.end, margin = 8.dp) } ) NetworkImage( url = imageUrl, modifier = Modifier.constrainAs(image) { centerVerticallyTo(parent) start.linkTo(parent.start, margin = 16.dp) top.linkTo(parent.top, margin = 16.dp) bottom.linkTo(parent.bottom, margin = 16.dp) } ) } }
  58. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { Card(

    modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp), shape = RoundedCornerShape(4.dp) ) { ConstraintLayout { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, modifier = Modifier.constrainAs(title) { start.linkTo(image.end, margin = 8.dp) top.linkTo(image.top) } ) CustomTextComponent( displayText = subtitleText, modifier = Modifier.constrainAs(subtitle) { bottom.linkTo(image.bottom) start.linkTo(image.end, margin = 8.dp) } ) NetworkImage( url = imageUrl, modifier = Modifier.constrainAs(image) { centerVerticallyTo(parent) start.linkTo(parent.start, margin = 16.dp) top.linkTo(parent.top, margin = 16.dp) bottom.linkTo(parent.bottom, margin = 16.dp) }) } } }
  59. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { Card(

    modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp), shape = RoundedCornerShape(4.dp) ) { ConstraintLayout { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, modifier = Modifier.constrainAs(title) { start.linkTo(image.end, margin = 8.dp) top.linkTo(image.top) } ) CustomTextComponent( displayText = subtitleText, modifier = Modifier.constrainAs(subtitle) { bottom.linkTo(image.bottom) start.linkTo(image.end, margin = 8.dp) } ) NetworkImage( url = imageUrl, modifier = Modifier.constrainAs(image) { centerVerticallyTo(parent) start.linkTo(parent.start, margin = 16.dp) top.linkTo(parent.top, margin = 16.dp) bottom.linkTo(parent.bottom, margin = 16.dp) }) } } }
  60. None
  61. Example: List

  62. None
  63. None
  64. www.JetpackCompose.app

  65. @Composable fun ListComponent(superheroList: List<Person>) { }

  66. @Composable fun ListComponent(superheroList: List<Person>) { LazyColumnItems(items = superheroList) { person

    -> } }
  67. @Composable fun ListComponent(superheroList: List<Person>) { LazyColumnItems(items = superheroList) { person

    -> SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } }
  68. @Composable fun ListComponent(superheroList: List<Person>) { LazyColumnItems(items = superheroList) { person

    -> SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } }
  69. Example: Click

  70. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String )

    { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp), shape = RoundedCornerShape(4.dp) ) { .... .... } }
  71. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel:

    SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.cl shape = RoundedCornerShape(4.dp) ) { .... .... } }
  72. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel:

    SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.cl shape = RoundedCornerShape(4.dp) ) { .... .... } } clip clickable clipToBounds
  73. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel:

    SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.clickable { viewModel.updateSelectedSuperhero() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }
  74. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, viewModel:

    SuperheroViewModel ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.clickable { viewModel.updateSelectedSuperhero() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }
  75. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, onClick:

    () -> Unit ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.clickable { onClick() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }
  76. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String, onClick:

    () -> Unit ) { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp) + Modifier.clickable { onClick() }, shape = RoundedCornerShape(4.dp) ) { .... .... } }
  77. Example: Pinch-to-Zoom & Drag

  78. @Composable fun ZoomableImageComponent(imageUrl: String) { }

  79. @Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state {

    1f } var panOffset by state { Offset(0f, 0f) } }
  80. @Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state {

    1f } var panOffset by state { Offset(0f, 0f) } Box(gravity = Alignment.Center) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize() ) } }
  81. @Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state {

    1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize() + Modifier.drawLayer( scaleX = scale, scaleY = scale ) ) } }
  82. @Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state {

    1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }) + Modifier.rawDragGestureFilter( object : DragObserver { override fun onDrag(dragDistance: Offset): Offset { panOffset = panOffset.plus(dragDistance) return super.onDrag(dragDistance) } }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize() + Modifier.drawLayer( scaleX = scale, scaleY = scale, translationX = panOffset.x, translationY = panOffset.y ) ) } }
  83. @Composable fun ZoomableImageComponent(imageUrl: String) { var scale by state {

    1f } var panOffset by state { Offset(0f, 0f) } Box( gravity = Alignment.Center, modifier = Modifier.zoomable(onZoomDelta = { scale *= it }) + Modifier.rawDragGestureFilter( object : DragObserver { override fun onDrag(dragDistance: Offset): Offset { panOffset = panOffset.plus(dragDistance) return super.onDrag(dragDistance) } }) ) { NetworkImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize() + Modifier.drawLayer( scaleX = scale, scaleY = scale, translationX = panOffset.x, translationY = panOffset.y ) ) } }
  84. Interoperability

  85. Example: Compose in Classic Android

  86. class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) } }
  87. class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) val containerLayout = findViewById(R.id.frame_container) containerLayout.setContent(Recomposer.current()) { SimpleRowComponent() } } }
  88. class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) val containerLayout = findViewById(R.id.frame_container) containerLayout.setContent(Recomposer.current()) { SimpleRowComponent() } } }
  89. Example: Classic Android in Compose

  90. <?xml version="1.0" encoding="utf-8"?> <com.vinaygaba.creditcardview.CreditCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id=“@+id/credit_card" android:layout_width="fill_parent" android:layout_height="225dp" app:cardName="John

    Doe" app:cardNumber="5500005555555559" app:cardNumberFormat="masked_all_but_last_four" app:expiryDate="02/22" app:putChip="true" app:type=“auto" /> credit_card.xml
  91. @Composable fun AndroidInCompose() { AndroidView(R.layout.credit_card, modifier = Modifier.padding(16.dp)) }

  92. Resources

  93. https://bit.ly/ComposeByExample

  94. https://www.JetpackCompose.app/

  95. None