Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

2013 2014 2015 2016 2017 2018 2019 2020 2021 2012 2022

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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”

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Common traits

Slide 26

Slide 26 text

Common traits › Declarative

Slide 27

Slide 27 text

Common traits › Declarative › Component-based

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Jetpack Compose

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Let’s get ticking

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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)

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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)

Slide 66

Slide 66 text

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)

Slide 67

Slide 67 text

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)

Slide 68

Slide 68 text

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)

Slide 69

Slide 69 text

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)

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Animated

Slide 75

Slide 75 text

Polished

Slide 76

Slide 76 text

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/

Slide 77

Slide 77 text

zsmb13 zsmb.co/talks

Slide 78

Slide 78 text

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