[I/O Extended 2019 Pangyo] Introduction to Jetpack Compose

A presentation at I/O Extended 2019 Pangyo in June 2019 in by Na Yoon Ho

Slide 1

Slide 1

Introduction to Jetpack Compose Declarative UI toolkit for Android

Slide 2

Slide 2

About me 3 yrs. 나윤호 Riiid! velog.io/@tura 19. 04~ @turastory

Slide 3

Slide 3

  1. 05
  2. 04
  3. 05
  4. 05
  5. 06

Slide 4

Slide 4

  1. 05
  2. 04
  3. 05
  4. 05 Declarative
  5. 06

Slide 5

Slide 5

Declarative UI What is Declarative UI Programming?

Slide 6

Slide 6

Imperative Specify HOW Eager Evaluation

Slide 7

Slide 7

Imperative Specify HOW Eager Evaluation Declarative Specify WHAT Lazy Evaluation

Slide 8

Slide 8

Why Declarative? More and more complex UI Animations, Transitions Focus on What to do, rather than how

Slide 9

Slide 9

A Quick Look Say hello to Jetpack Compose

Slide 10

Slide 10

Counter App Click -> +1

Slide 11

Slide 11

Usual Way val binding = DataBindingUtil .setContentView<ActivityCounterBinding>( this, R.layout.activity_counter ) var count = 0 binding.incrementButton.setOnClickListener { binding.counterText.text = getString(R.string.counter, !++count) }

Slide 12

Slide 12

Rx Way val binding = DataBindingUtil .setContentView<ActivityCounterBinding>( this, R.layout.activity_counter ) var count = 0 binding.incrementButton .clicks() .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ binding.counterText.text = getString(R.string.counter, count) }, Throwable!::printStackTrace) .disposedBy()

Slide 13

Slide 13

Compose Way setContent { Center { Column { val count = +state { 0 } Text( text = “Count: ${count.value}”, style = +themeTextStyle { this.h3 } ) Button( text = “Increment”, onClick = { count.value += 1 } ) } } }

Slide 14

Slide 14

Backgrounds What brings us to Jetpack Compose?

Slide 15

Slide 15

Problems 1. Tightly coupled with framework 2. Distributed codes 3. Inconsistent data flow

Slide 16

Slide 16

Jetpack Compose Let’s dive into Compose!

Slide 17

Slide 17

  1. UI as a function

Slide 18

Slide 18

  1. UI as a function Integer fun square(x: Int) = x * x Key Concepts Integer

Slide 19

Slide 19

  1. UI as a function String fun Greeting(name: String) = println(“Hello, $name”) Key Concepts stdout

Slide 20

Slide 20

  1. UI as a function Data fun Greeting(name: String) = Text(“Hello, $name”) Key Concepts UI

Slide 21

Slide 21

  1. UI as a function Mark as a composable widget @Composable fun Greeting(name: String) = Text(“Hello, $name”) Key Concepts

Slide 22

Slide 22

  1. UI as a function @Composable fun Greeting(name: String) = Text(“Hello, $name”) UI Hierarchy Key Concepts

Slide 23

Slide 23

  1. UI as a function @Composable fun Loading() = CircularProgressIndicator() @Composable fun FriendWidget(friend: Friend) { val image = asyncLoad(placeHolder) { loadImage(friend.url) } Image(image) Text( text = friend.name, style = +themeTextStyle { h4 } ) } @Composable fun friendsList(state: FriendsListState) { val friends = state.friends if (friends.isEmpty()) { Loading() } else { friends.forEach(this!::FriendWidget) } } Kotlin Super Power

Slide 24

Slide 24

  1. UI as a function Kotlin Super Power @Composable fun Loading() = CircularProgressIndicator() @Composable fun FriendWidget(friend: Friend) { val image = asyncLoad(placeHolder) { loadImage(friend.url) } Image(image) Text( text = friend.name, style = +themeTextStyle { h4 } ) } @Composable fun friendsList(state: FriendsListState) { val friends = state.friends if (friends.isEmpty()) { Loading() } else { friends.forEach(this!::FriendWidget) } } Child Component Composables are composable

Slide 25

Slide 25

  1. UI as a function Kotlin Super Power @Composable fun Loading() = CircularProgressIndicator() @Composable fun FriendWidget(friend: Friend) { val image = asyncLoad(placeHolder) { loadImage(friend.url) } Image(image) Text( text = friend.name, style = +themeTextStyle { h4 } ) } @Composable fun friendsList(state: FriendsListState) { val friends = state.friends if (friends.isEmpty()) { Loading() } else { friends.forEach(this!::FriendWidget) } } Conditions / Loop

Slide 26

Slide 26

  1. UI as a function Kotlin Super Power @Composable fun Loading() = CircularProgressIndicator() @Composable fun FriendWidget(friend: Friend) { val image = asyncLoad(placeHolder) { loadImage(friend.url) } Image(image) Text( text = friend.name, style = +themeTextStyle { h4 } Coroutines (Async ) } @Composable fun friendsList(state: FriendsListState) { val friends = state.friends if (friends.isEmpty()) { Loading() } else { friends.forEach(this!::FriendWidget) } } Loading)

Slide 27

Slide 27

  1. UI as a function @Model data class Friend( var name: String ) @Composable fun App() { val friend by +state { Friend(“tura”) } FriendWidget(friend) Button( text = “Click me!”, onClick = { friend.name = randomName() } ) } Update UI

Slide 28

Slide 28

  1. UI as a function @Model data class Friend( var name: String ) Update UI Use @Model to make class observable @Composable fun App() { val friend by +state { Friend(“tura”) } FriendWidget(friend) Button( text = “Click me!”, onClick = { friend.name = randomName() } ) }

Slide 29

Slide 29

  1. UI as a function Update UI @Model data class Friend( var name: String ) @Composable fun App() { val friend by +state { Friend(“tura”) } FriendWidget(friend) Button( text = “Click me!”, onClick = { friend.name = randomName() } ) } Define local state

Slide 30

Slide 30

  1. UI as a function Update UI @Model data class Friend( var name: String ) @Composable fun App() { val friend by +state { Friend(“tura”) } FriendWidget(friend) Button( text = “Click me!”, onClick = { friend.name = randomName() } ) } Changing value causes Recomposition

Slide 31

Slide 31

  1. UI as a function @Composable fun PhoneInputWidget(data: InputData) { EditableText( text = data.phoneNumber, onChange = { newValue !-> newValue .let(this!::filterInvalidPhoneCharacters) .let(this!::formatPhone) .let { data.phoneNumber = it } } ) } State Management

Slide 32

Slide 32

  1. UI as a function State Management @Composable fun PhoneInputWidget(data: InputData) { EditableText( text = data.phoneNumber, No internal state onChange = { newValue !-> Always receive data from outer world newValue .let(this!::filterInvalidPhoneCharacters) .let(this!::formatPhone) .let { data.phoneNumber = it } } ) }

Slide 33

Slide 33

  1. UI as a function State Management @Composable fun PhoneInputWidget(data: InputData) { EditableText( text = data.phoneNumber, No internal state onChange = { newValue !-> Always receive data from outer world newValue .let(this!::filterInvalidPhoneCharacters) .let(this!::formatPhone) .let { data.phoneNumber = it } } ) } … And make validation BEFORE update UI

Slide 34

Slide 34

  1. No more view

Slide 35

Slide 35

  1. No more view Directly draw onto Canvas Screenshot from Thijs Suijten - Diving into Jetpack Compose

Slide 36

Slide 36

  1. No more view Attributes are just normal classes

Slide 37

Slide 37

  1. Exploring APIs

Slide 38

Slide 38

  1. Exploring APIs @Composable Mark function as a Composable widget @Model Mark class as an Observable Data for Composable Widgets Annotations

Slide 39

Slide 39

  1. Exploring APIs Effects Effect Codes that should be executed during executing composition Composition Phase @Composable fun CounterWidget() { Log.d(“Counter”, “CounterWidget start”) val count = +state { 0 } Text(text = “Count: ${count.value}”) Button(onClick = { count.value!++ }) } Execution Phase

Slide 40

Slide 40

  1. Exploring APIs Effects Effect Codes that should be executed during executing composition Composition Phase @Composable fun CounterWidget() { Log.d(“Counter”, “CounterWidget start”) val count = +state { 0 } Text(text = “Count: ${count.value}”) Button(onClick = { count.value!++ }) } Execution Phase

Slide 41

Slide 41

  1. Exploring APIs Effects Effect Codes that should be executed during executing composition Composition Phase @Composable fun CounterWidget() { Log.d(“Counter”, “CounterWidget start”) val count = +state { 0 } Text(text = “Count: ${count.value}”) Button(onClick = { count.value!++ }) } Execution Phase Use +state to define local state

Slide 42

Slide 42

  1. Exploring APIs memo Used when some value is needed state memo { State(init()) } Used when local mutable state is needed ambient Used when data flow is too complex Effects

Slide 43

Slide 43

  1. Exploring APIs memo Used when some value is needed state memo { State(init()) } Used when local mutable state is needed ambient Used when data flow is too complex Effects

Slide 44

Slide 44

  1. Exploring APIs memo state Used when some value is needed state Effects memo { State(init()) } Used when local mutable state is needed ambient Used when data flow is too complex

Slide 45

Slide 45

  1. Exploring APIs memo ambient Used when some value is needed state Effects memo { State(init()) } Used when local mutable state is needed ambient Used when data flow is too complex

Slide 46

Slide 46

  1. Exploring APIs Effects Widget onActive onActive Run code when widget starts showing onDispose Change onCommit Change onCommit Run code when widget is not showing anymore onCommit Run code when composition happens onDispose

Slide 47

Slide 47

  1. Exploring APIs IDE Support KTX Tags <AndroidCraneView ref=rootRef> var reference: CompositionReference? = null var cc: CompositionContext? = null SFC? Stateless Functional Component React?

Slide 48

Slide 48

Try Jetpack Compose Try it out by yourself!

Slide 49

Slide 49

  1. Setup repo 2. Get source 3. Launch Android Studio 4. Browse samples

Slide 50

Slide 50

  1. Setup repo λ curl https:!//storage.googleapis.com/git-repo-downloads/repo > repo λ chmod a+x repo λ mv repo ~/bin λ echo ‘PATH=$HOME/bin:$PATH’ !>> .zshrc # For bash λ echo ‘PATH=$HOME/bin:$PATH’ !>> .bashrc
  2. Get source 3. Launch Android Studio 4. Browse samples

Slide 51

Slide 51

  1. Setup repo 2. Get source λ mkdir androidx-master-dev λ cd android-master-dev # Get source for branch ‘androidx-master-dev’ λ repo init -u https:!//android.googlesource.com/platform/manifest \ -b androidx-master-dev # Sync with remote λ repo sync -j8 -c
  2. Launch Android Studio 4. Browse samples

Slide 52

Slide 52

  1. Setup repo 2. Get source 3. Launch Android Studio # Launch specific version of Android Studio λ cd androidx-master-dev/frameworks/support/ui λ ./studiow When version is different…
  2. Browse samples

Slide 53

Slide 53

  1. Setup repo 2. Get source 3. Launch Android Studio 4. Browse samples :ui-demos :ui-material-studies How can I use Compose :compose-runtime How Compose works :compose-plugin-cli :compose-plugin-ide Behind the scenes - Resolver, Quick Fix

Slide 54

Slide 54

Recap

Slide 55

Slide 55

Recap New Declarative UI Toolkit for Android No more XML, Views - Just Kotlin Pre-alpha

Slide 56

Slide 56

We are hiring!!

Slide 57

Slide 57

!iiid!

Slide 58

Slide 58

!iiid! / get rid of sth /

Slide 59

Slide 59

https://career.riiid.app/android-developer 100% Kotlin RxJava Gradle Kotlin DSL Fun !iiid!

Slide 60

Slide 60

Q&A