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 we need
    Jetpack Compose?

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  11. Example: Hello World

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. fun ComponentActivity.setContent(
    recomposer: Recomposer = Recomposer.current(),
    content: @Composable () -> Unit
    ): Composition {
    // Some magic ✨
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. @Composable
    fun CustomTextComponent(displayText: String) {
    Text(
    text = displayText,
    style = TextStyle(
    fontSize = 18.sp,
    fontFamily = FontFamily.Monospace
    )
    )
    }
    @Preview
    @Composable
    fun CustomTextComponentPreview() {
    CustomTextComponent("Hello World")
    }

    View full-size slide

  23. Example: Show an Image

    View full-size slide

  24. @Composable
    fun DrawableImage() {
    }

    View full-size slide

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

    View full-size slide

  26. @Composable
    fun DrawableImage() {
    val image = loadImageResource(R.drawable.lena)
    image.resource.resource?.let {
    Image(asset = it, modifier = Modifier.preferredSize(200.dp))
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  29. Example: Alert Dialog

    View full-size slide

  30. // 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()

    View full-size slide

  31. @Composable
    fun AlertDialogComponent() {
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. @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")
    }
    }
    )
    }
    }

    View full-size slide

  35. @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")
    }
    }
    )
    }
    }

    View full-size slide

  36. @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")
    }
    }
    )
    }
    }

    View full-size slide

  37. Example: Simple Layouts

    View full-size slide

  38. 1
    Column
    1
    Row
    2

    View full-size slide

  39. @Composable
    fun ImageWithTitleSubtitleComponent() {
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. @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")
    }
    }
    }

    View full-size slide

  43. @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)
    }
    }
    }

    View full-size slide

  44. @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)
    }
    }
    }

    View full-size slide

  45. Example: ConstraintLayout

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  49. @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,
    )
    }
    }

    View full-size slide

  50. @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)
    }
    )
    }
    }

    View full-size slide

  51. @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)
    })
    }
    }
    }

    View full-size slide

  52. @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)
    })
    }
    }
    }

    View full-size slide

  53. Example: List

    View full-size slide

  54. www.JetpackCompose.app

    View full-size slide

  55. @Composable
    fun ListComponent(superheroList: List) {
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  59. Example: Click

    View full-size slide

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

    View full-size slide

  61. @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)
    ) {
    ....
    ....
    }
    }

    View full-size slide

  62. @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

    View full-size slide

  63. @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)
    ) {
    ....
    ....
    }
    }

    View full-size slide


  64. @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)
    ) {
    ....
    ....
    }
    }

    View full-size slide

  65. @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)
    ) {
    ....
    ....
    }
    }

    View full-size slide

  66. @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)
    ) {
    ....
    ....
    }
    }

    View full-size slide

  67. Example: Pinch-to-Zoom &
    Drag

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  70. @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()
    )
    }
    }

    View full-size slide

  71. @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
    )
    )
    }
    }

    View full-size slide

  72. @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
    )
    )
    }
    }

    View full-size slide

  73. @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
    )
    )
    }
    }

    View full-size slide

  74. Interoperability

    View full-size slide

  75. Example: Compose in
    Classic Android

    View full-size slide

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

    View full-size slide

  77. 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()
    }
    }
    }

    View full-size slide

  78. 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()
    }
    }
    }

    View full-size slide

  79. Example: Classic Android in
    Compose

    View full-size slide


  80. 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

    View full-size slide

  81. @Composable
    fun AndroidInCompose() {
    AndroidView(R.layout.credit_card, modifier = Modifier.padding(16.dp))
    }

    View full-size slide

  82. https://bit.ly/ComposeByExample

    View full-size slide

  83. https://www.JetpackCompose.app/

    View full-size slide