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

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

vinaygaba

July 23, 2020
Tweet

More Decks by vinaygaba

Other Decks in Technology

Transcript

  1. 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
  2. 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
  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. 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
  7. 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
  8. 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
  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. @Composable fun CustomTextComponent(displayText: String) { Text( text = displayText, style

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

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

    = TextStyle( fontSize = 18.sp, fontFamily = FontFamily.Monospace ) ) } @Preview @Composable fun CustomTextComponentPreview() { CustomTextComponent("Hello World") }
  13. @Composable fun DrawableImage(@DrawableRes resId: Int) { val image = loadImageResource(resId)

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

    image.resource.resource?.let { Image(asset = it, modifier = Modifier.preferredSize(200.dp)) } }
  15. // 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()
  16. @Composable fun AlertDialogComponent() { var showPopup by state { false

    } Button(onClick = { showPopup = true }, backgroundColor = Color.DarkGray) { Text(text = "Click Me") } }
  17. @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") } } ) } }
  18. @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") } } ) } }
  19. @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") } } ) } }
  20. Row

  21. @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") } } }
  22. @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) } } }
  23. @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) } } }
  24. @Composable fun SimpleRowComponent(titleText: String, subtitleText: String, imageUrl: String) { ConstraintLayout

    { val (title, subtitle, image) = createRefs() CustomTextComponent( displayText = titleText, ) CustomTextComponent( displayText = subtitleText, ) NetworkImage( url = imageUrl, ) } }
  25. @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, ) } }
  26. @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) } ) } }
  27. @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) }) } } }
  28. @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) }) } } }
  29. @Composable fun ListComponent(superheroList: List<Person>) { LazyColumnItems(items = superheroList) { person

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

    -> SimpleRowComponent( person.name, person.age, person.profilePictureUrl ) } }
  31. @Composable fun SimpleRowComponent( titleText: String, subtitleText: String, imageUrl: String )

    { Card( modifier = Modifier.fillMaxWidth() + Modifier.padding(8.dp), shape = RoundedCornerShape(4.dp) ) { .... .... } }
  32. @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) ) { .... .... } }
  33. @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
  34. @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) ) { .... .... } }
  35. @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) ) { .... .... } }
  36. @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) ) { .... .... } }
  37. @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) ) { .... .... } }
  38. @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() ) } }
  39. @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 ) ) } }
  40. @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 ) ) } }
  41. @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 ) ) } }
  42. class ComposeInClassicAndroidActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_compose_in_classic_android) } }
  43. 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() } } }
  44. 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() } } }