Introduction to Jetpack Compose Declarative UI toolkit for Android

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

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

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

Declarative UI What is Declarative UI Programming?

Imperative Specify HOW Eager Evaluation

Imperative Specify HOW Eager Evaluation Declarative Specify WHAT Lazy Evaluation

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

A Quick Look Say hello to Jetpack Compose

Counter App Click -> +1

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

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

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

Backgrounds What brings us to Jetpack Compose?

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

Jetpack Compose Let’s dive into Compose!

  1. UI as a function

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

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

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

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

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

  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

  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

  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

  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)

  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

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

  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

  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

  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

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

  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

  1. No more view

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

  1. No more view Attributes are just normal classes

  1. Exploring APIs

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

  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

  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

  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

  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

  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

  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

  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

  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

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

Try Jetpack Compose Try it out by yourself!

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

  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

  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

  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

  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

Recap

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

We are hiring!!

!iiid!

!iiid! / get rid of sth /

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

Q&A