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

It's Compose O'Clock on Android (DevFest Live 2021)

It's Compose O'Clock on Android (DevFest Live 2021)

Jetpack Compose is changing how UI is being built on Android. This new toolkit is replacing the now 10-year-old, somewhat tedious View system with a declarative, functional approach, and promises to simplify and speed up UI development.

This is happening at the same time as Flutter continuously gaining popularity, and iOS getting its own declarative UI APIs with SwiftUI.

This session explains what Jetpack Compose is, and then shows a quick example of interesting UI with it.

Marton Braun

June 03, 2021
Tweet

More Decks by Marton Braun

Other Decks in Technology

Transcript

  1. Márton Braun
    zsmb.co
    zsmb13
    It’s Compose
    O'Clock on
    Android

    View Slide

  2. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022

    View Slide

  3. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React

    View Slide

  4. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React

    View Slide

  5. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native

    View Slide

  6. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native

    View Slide

  7. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter

    View Slide

  8. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter

    View Slide

  9. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2

    View Slide

  10. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2

    View Slide

  11. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI

    View Slide

  12. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI

    View Slide

  13. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  14. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  15. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  16. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  17. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  18. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    Jetpack Compose alpha
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”

    View Slide

  19. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    Jetpack Compose alpha
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”
    Jetpack Compose beta

    View Slide

  20. Jetpack Compose alpha
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”
    Jetpack Compose beta
    2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022

    View Slide

  21. Jetpack Compose alpha
    Jetpack Compose announced
    React
    React Native
    Flutter
    Flutter 2
    SwiftUI
    SwiftUI “2.0”
    Jetpack Compose beta
    2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022

    View Slide

  22. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    2008
    2009
    2010
    2011
    2007

    View Slide

  23. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    2008
    2009
    2010
    2011
    2007

    View Slide

  24. 2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2012
    2022
    2008
    2009
    2010
    2011
    2007

    View Slide

  25. Common traits

    View Slide

  26. Common traits
    › Declarative

    View Slide

  27. Common traits
    › Declarative
    › Component-based

    View Slide

  28. Common traits
    › Declarative
    › Component-based
    › UI is a function of state

    View Slide

  29. Common traits
    › Declarative
    › Component-based
    › UI is a function of state
    › Mostly blue

    View Slide

  30. Common traits
    › Declarative
    › Component-based
    › UI is a function of state
    › Mostly blue
    › Quick iteration, fast previews

    View Slide

  31. Common traits
    › Declarative
    › Component-based
    › UI is a function of state
    › Mostly blue
    › Quick iteration, fast previews
    › Multiplatform ambitions

    View Slide

  32. Jetpack Compose

    View Slide

  33. Jetpack Compose
    › Built with Composable functions
    › Eliminates the need to sync UI state

    View Slide

  34. Jetpack Compose
    › Built with Composable functions
    › Eliminates the need to sync UI state
    › Unbundled
    › Not tied to OS versions

    View Slide

  35. Jetpack Compose
    › Built with Composable functions
    › Eliminates the need to sync UI state
    › Unbundled
    › Not tied to OS versions
    › All Kotlin
    › Awesome!

    View Slide

  36. Jetpack Compose
    › Built with Composable functions
    › Eliminates the need to sync UI state
    › Unbundled
    › Not tied to OS versions
    › All Kotlin
    › Awesome!
    › Two-way interoperability
    › Works with existing UI code

    View Slide

  37. Let’s get ticking

    View Slide

  38. A single number
    @Composable
    fun Number(value: Int) {
    }

    View Slide

  39. A single number
    @Composable
    fun Number(value: Int) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    modifier = Modifier.size(40.dp)
    )
    }

    View Slide

  40. A single number
    @Composable
    fun Number(value: Int) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    modifier = Modifier.size(40.dp)
    )
    }

    View Slide

  41. A single number
    @Composable
    fun Number(value: Int) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    modifier = Modifier.size(40.dp)
    )
    }
    Number(3)

    View Slide

  42. A single number
    @Composable
    fun Number(value: Int) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    modifier = Modifier
    )
    }
    3
    Number(3)
    .size(40.dp)

    View Slide

  43. A single number
    @Composable
    fun Number(value: Int) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    modifier = Modifier
    .background(Color.Black)
    )
    }
    Number(3)
    .size(40.dp)
    3

    View Slide

  44. A single number
    text = value.toString(),
    fontSize = 20.sp,
    )
    }
    Number(3)
    modifier = Modifier
    .size(40.dp)
    .background(Color.Black)
    Text(
    @Composable
    fun Number(value: Int) {
    3

    View Slide

  45. A single number
    Box(
    ) {
    text = value.toString(),
    fontSize = 20.sp,
    )
    }
    }
    Number(3)
    modifier = Modifier
    .size(40.dp)
    .background(Color.Black)
    Text(
    @Composable
    fun Number(value: Int) {
    3

    View Slide

  46. A single number
    Box(
    contentAlignment = Alignment.Center,
    ) {
    text = value.toString(),
    fontSize = 20.sp,
    )
    }
    }
    Number(3)
    3
    modifier = Modifier
    .size(40.dp)
    .background(Color.Black)
    Text(
    @Composable
    fun Number(value: Int) {

    View Slide

  47. backgroundColor
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background(
    A single number
    Number(3)
    Color.Black
    @Composable
    fun Number(value: Int) {
    )
    }
    }
    ) 3

    View Slide

  48. A single number
    Number(3)
    @Composable
    fun Number(value: Int, active: Boolean) {
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background( )
    val backgroundColor = if (active) {
    MaterialTheme.colors.primary
    } else {
    MaterialTheme.colors.primaryVariant
    }
    )
    }
    }
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    backgroundColor
    color = Color.White,
    3

    View Slide

  49. A single number
    Column {
    Number(3, active = true)
    Number(7, active = false)
    }
    @Composable
    fun Number(value: Int, active: Boolean) {
    val backgroundColor = if (active) {
    MaterialTheme.colors.primary
    } else {
    MaterialTheme.colors.primaryVariant
    }
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background(backgroundColor)
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    color = Color.White,
    )
    }
    }
    3
    7

    View Slide

  50. A single number
    Column {
    Number(3, active = true)
    Number(7, active = false)
    }
    @Composable
    fun Number(value: Int, active: Boolean) {
    val backgroundColor = if (active) {
    MaterialTheme.colors.primary
    } else {
    MaterialTheme.colors.primaryVariant
    }
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background(backgroundColor)
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    color = Color.White,
    )
    }
    }
    3
    7

    View Slide

  51. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    3
    7

    View Slide

  52. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    3
    7

    View Slide

  53. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    3
    7

    View Slide

  54. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    3
    7

    View Slide

  55. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    NumberColumn(range = 0..9, current = 5)
    3
    7

    View Slide

  56. A column of digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    NumberColumn(range = 0..9, current = 5)
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6

    View Slide

  57. A complete clock
    @Composable
    fun Clock(time: Time) {
    Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.Center,
    verticalAlignment = Alignment.CenterVertically,
    ) {
    NumberColumn(0..2, time.hours / 10)
    NumberColumn(0..9, time.hours % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.minutes / 10)
    NumberColumn(0..9, time.minutes % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.seconds / 10)
    NumberColumn(0..9, time.seconds % 10)
    }
    }

    View Slide

  58. A complete clock
    @Composable
    fun Clock(time: Time) {
    Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.Center,
    verticalAlignment = Alignment.CenterVertically,
    ) {
    NumberColumn(0..2, time.hours / 10)
    NumberColumn(0..9, time.hours % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.minutes / 10)
    NumberColumn(0..9, time.minutes % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.seconds / 10)
    NumberColumn(0..9, time.seconds % 10)
    }
    }

    View Slide

  59. A complete clock
    @Composable
    fun Clock(time: Time) {
    Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.Center,
    verticalAlignment = Alignment.CenterVertically,
    ) {
    NumberColumn(0..2, time.hours / 10)
    NumberColumn(0..9, time.hours % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.minutes / 10)
    NumberColumn(0..9, time.minutes % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.seconds / 10)
    NumberColumn(0..9, time.seconds % 10)
    }
    }

    View Slide

  60. A complete clock
    @Composable
    fun Clock(time: Time) {
    Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.Center,
    verticalAlignment = Alignment.CenterVertically,
    ) {
    NumberColumn(0..2, time.hours / 10)
    NumberColumn(0..9, time.hours % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.minutes / 10)
    NumberColumn(0..9, time.minutes % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.seconds / 10)
    NumberColumn(0..9, time.seconds % 10)
    }
    }
    Clock(Time(14, 15, 59))

    View Slide

  61. A complete clock
    @Composable
    fun Clock(time: Time) {
    Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.Center,
    verticalAlignment = Alignment.CenterVertically,
    ) {
    NumberColumn(0..2, time.hours / 10)
    NumberColumn(0..9, time.hours % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.minutes / 10)
    NumberColumn(0..9, time.minutes % 10)
    Spacer(Modifier.size(16.dp))
    NumberColumn(0..5, time.seconds / 10)
    NumberColumn(0..9, time.seconds % 10)
    }
    }
    Clock(Time(14, 15, 59))
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    5
    2
    0
    1
    4
    3
    5
    0
    1
    2

    View Slide

  62. Aligning digits
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    5
    2
    0
    1
    4
    3
    5
    0
    1
    2
    Clock(Time(14, 15, 59))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    Column(
    Modifier.clip(RoundedCornerShape(percent = 25))
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {

    View Slide

  63. Aligning digits
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    5
    2
    0
    1
    4
    3
    5
    0
    1
    2
    Clock(Time(14, 15, 59))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    Column(
    Modifier
    .clip(RoundedCornerShape(percent = 25))
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    val mid = (range.last - range.first) / 2f
    val offset = 40.dp * (mid - current)
    .offset(y = offset)

    View Slide

  64. Aligning digits
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    val mid = (range.last - range.first) / 2f
    val offset = 40.dp * (mid - current)
    Column(
    Modifier
    .offset(y = offset)
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    6
    5
    8
    7
    6
    2
    4
    3
    6
    5
    8
    7
    6
    2
    0
    1
    4
    3
    5
    2
    0
    1
    4
    3
    5
    0
    1
    2
    Clock(Time(14, 15, 59))

    View Slide

  65. Driving the clock
    fun currentTime(): Time {
    val cal = Calendar.getInstance()
    return Time(
    hours = cal.get(Calendar.HOUR_OF_DAY),
    minutes = cal.get(Calendar.MINUTE),
    seconds = cal.get(Calendar.SECOND),
    )
    }
    var time by remember { mutableStateOf(currentTime()) }
    LaunchedEffect(0) {
    while (true) {
    time = currentTime()
    delay(1000)
    }
    }
    Clock(time)

    View Slide

  66. Driving the clock
    fun currentTime(): Time {
    val cal = Calendar.getInstance()
    return Time(
    hours = cal.get(Calendar.HOUR_OF_DAY),
    minutes = cal.get(Calendar.MINUTE),
    seconds = cal.get(Calendar.SECOND),
    )
    }
    var time by remember { mutableStateOf(currentTime()) }
    LaunchedEffect(0) {
    while (true) {
    time = currentTime()
    delay(1000)
    }
    }
    Clock(time)

    View Slide

  67. Driving the clock
    fun currentTime(): Time {
    val cal = Calendar.getInstance()
    return Time(
    hours = cal.get(Calendar.HOUR_OF_DAY),
    minutes = cal.get(Calendar.MINUTE),
    seconds = cal.get(Calendar.SECOND),
    )
    }
    var time by remember { mutableStateOf(currentTime()) }
    LaunchedEffect(0) {
    while (true) {
    time = currentTime()
    delay(1000)
    }
    }
    Clock(time)

    View Slide

  68. Driving the clock
    fun currentTime(): Time {
    val cal = Calendar.getInstance()
    return Time(
    hours = cal.get(Calendar.HOUR_OF_DAY),
    minutes = cal.get(Calendar.MINUTE),
    seconds = cal.get(Calendar.SECOND),
    )
    }
    var time by remember { mutableStateOf(currentTime()) }
    LaunchedEffect(0) {
    while (true) {
    time = currentTime()
    delay(1000)
    }
    }
    Clock(time)

    View Slide

  69. Driving the clock
    fun currentTime(): Time {
    val cal = Calendar.getInstance()
    return Time(
    hours = cal.get(Calendar.HOUR_OF_DAY),
    minutes = cal.get(Calendar.MINUTE),
    seconds = cal.get(Calendar.SECOND),
    )
    }
    var time by remember { mutableStateOf(currentTime()) }
    LaunchedEffect(0) {
    while (true) {
    time = currentTime()
    delay(1000)
    }
    }
    Clock(time)

    View Slide

  70. Animations
    @Composable
    fun Number(value: Int, active: Boolean) {
    val backgroundColor =
    if (active) MaterialTheme.colors.primary
    else MaterialTheme.colors.primaryVariant
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background(backgroundColor),
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    color = Color.White,
    )
    }
    }

    View Slide

  71. Animations
    @Composable
    fun Number(value: Int, active: Boolean) {
    val backgroundColor by animateColorAsState(
    if (active) MaterialTheme.colors.primary
    else MaterialTheme.colors.primaryVariant
    )
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .size(40.dp)
    .background(backgroundColor),
    ) {
    Text(
    text = value.toString(),
    fontSize = 20.sp,
    color = Color.White,
    )
    }
    }

    View Slide

  72. Animations
    =
    Column(
    Modifier
    .offset(y = offset)
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    40.dp * (mid - current)
    val mid = (range.last - range.first) / 2f
    val offset

    View Slide

  73. Animations
    Column(
    Modifier
    .offset(y = offset)
    .clip(RoundedCornerShape(percent = 25))
    ) {
    range.forEach { num ->
    Number(num, num == current)
    }
    }
    }
    @Composable
    fun NumberColumn(
    range: IntRange,
    current: Int,
    ) {
    40.dp * (mid - current)
    val mid = (range.last - range.first) / 2f
    val offset by animateDpAsState(
    targetValue =
    )

    View Slide

  74. Animated

    View Slide

  75. Polished

    View Slide

  76. Resources
    • Compose O’Clock
     https://zsmb.co/compose-o-clock/
    • Source code on GitHub
     https://github.com/zsmb13/ComposeClock
    • Jetpack Compose First Impressions & Learning Resources
     https://getstream.io/blog/jetpack-compose-impressions-resources/

    View Slide

  77. zsmb13
    zsmb.co/talks

    View Slide

  78. zsmb.co/talks
    zsmb13
    Márton Braun
    It's Compose
    O'Clock on
    Android
    › Declarative,
    component-based
    › Quick iteration
    › Future of Android UI
    development

    View Slide