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. View Slide

  2. Why do we need
    Jetpack Compose?

    View 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 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 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 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 Slide

  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

    View Slide

  8. Examples

    View 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 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 Slide

  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

    View Slide

  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

    View Slide

  13. Example: Hello World

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. View Slide

  26. View Slide

  27. Example: Show an Image

    View Slide

  28. @Composable
    fun DrawableImage() {
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. Example: Alert Dialog

    View Slide

  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()

    View Slide

  35. @Composable
    fun AlertDialogComponent() {
    }

    View Slide

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

    View Slide

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

    View Slide

  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")
    }
    }
    )
    }
    }

    View Slide

  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")
    }
    }
    )
    }
    }

    View Slide

  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")
    }
    }
    )
    }
    }

    View Slide

  41. Example: Simple Layouts

    View Slide

  42. View Slide

  43. Row

    View Slide

  44. Row
    1 2

    View Slide

  45. 1
    Column
    1
    Row
    2

    View Slide

  46. @Composable
    fun ImageWithTitleSubtitleComponent() {
    }

    View Slide

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

    View Slide

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

    View Slide

  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")
    }
    }
    }

    View Slide

  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)
    }
    }
    }

    View Slide

  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)
    }
    }
    }

    View Slide

  52. Example: ConstraintLayout

    View Slide

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

    View Slide

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

    View Slide

  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,
    )
    }
    }

    View Slide

  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,
    )
    }
    }

    View Slide

  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)
    }
    )
    }
    }

    View Slide

  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)
    })
    }
    }
    }

    View Slide

  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)
    })
    }
    }
    }

    View Slide

  60. View Slide

  61. Example: List

    View Slide

  62. View Slide

  63. View Slide

  64. www.JetpackCompose.app

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  69. Example: Click

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide


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

    View Slide

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

    View Slide

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

    View Slide

  77. Example: Pinch-to-Zoom &
    Drag

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    )
    )
    }
    }

    View Slide

  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
    )
    )
    }
    }

    View Slide

  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
    )
    )
    }
    }

    View Slide

  84. Interoperability

    View Slide

  85. Example: Compose in
    Classic Android

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. Example: Classic Android in
    Compose

    View Slide


  90. 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 Slide

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

    View Slide

  92. Resources

    View Slide

  93. https://bit.ly/ComposeByExample

    View Slide

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

    View Slide

  95. View Slide