Slide 1

Slide 1 text

Speaker l 2022 찰스의 안드로이드 컨퍼런스 Jetpack Compose, 어디까지 알고 있을까? 지성빈

Slide 2

Slide 2 text

Speaker l 2022 찰스의 안드로이드 컨퍼런스 🏠 sungb.in 🏰 sungbin.land 🐙 github.com/jisungbin

Slide 3

Slide 3 text

2022 찰스의 안드로이드 컨퍼런스 목차 1. UI 2. Runtime 3. Snapshot System 4. LiveLiterals

Slide 4

Slide 4 text

2022 찰스의 안드로이드 컨퍼런스 시작하기 전에 •컴포즈 버전 1.2.0-beta02 기준 •코드 세부 사항은 생략

Slide 5

Slide 5 text

2022 찰스의 안드로이드 컨퍼런스 1. UI

Slide 6

Slide 6 text

2022 찰스의 안드로이드 컨퍼런스 Text Text("선택 불기능한 Text")

Slide 7

Slide 7 text

2022 찰스의 안드로이드 컨퍼런스 SelectionContainer Text("선택 불기능한 Text") SelectionContainer { Column { Text("선택 가능한 Text - 1") Text("선택 가능한 Text - 2") } }

Slide 8

Slide 8 text

2022 찰스의 안드로이드 컨퍼런스 DisableSelection Column { Text("선택 불가능한 Text") SelectionContainer { Column { Text("선택 가능한 Text - 1") Text("선택 가능한 Text - 2") DisableSelection { Text("다시 선택 불가능한 Text - 1") Text("다시 선택 불가능한 Text - 2") } Text("선택 가능한 Text - 3") Text("선택 가능한 Text - 4") } } }

Slide 9

Slide 9 text

2022 찰스의 안드로이드 컨퍼런스 ClickableText val annotatedText = buildAnnotatedString { append("클릭 가능한 Text") } ClickableText( text = annotatedText, onClick = { offset -> toast("클릭된 오프셋: $offset") } )

Slide 10

Slide 10 text

2022 찰스의 안드로이드 컨퍼런스 ClickableText val annotatedText = buildAnnotatedString { withAnnotation( tag = "URL", annotation = "https://sungbin.land" ) { withStyle( style = localTextStyle.copy( color = Color.Green, fontWeight = FontWeight.Bold ).toSpanStyle() ) { append("성빈랜드") } } append(" 바로가기") } onClick = { offset -> annotatedText.getStringAnnotations( tag = "URL", start = offset, end = offset ).firstOrNull()?.let { annotation -> val message = """ 클릭된 URL: ${annotation.item} 클릭된 오프셋: $offset """.trimIndent() toast(message) } }

Slide 11

Slide 11 text

2022 찰스의 안드로이드 컨퍼런스 LazyList key LazyColumn { items(count = 50) { number -> Text( modifier = Modifier.padding(vertical = 30.dp), text = "내 번호: $number" ) } }

Slide 12

Slide 12 text

2022 찰스의 안드로이드 컨퍼런스 LazyList key LazyColumn { items(count = 50) { number -> Text( modifier = Modifier.padding(vertical = 30.dp), text = "내 번호: $number" ) } }

Slide 13

Slide 13 text

2022 찰스의 안드로이드 컨퍼런스 LazyList key LazyColumn { items( count = 50, key = { number -> number } ) { number -> Text( modifier = Modifier.padding(vertical = 30.dp), text = "내 번호: $number" ) } }

Slide 14

Slide 14 text

2022 찰스의 안드로이드 컨퍼런스 LazyList contentType LazyColumn { items( count = 50, key = { number -> number }, contentType = { 0 } ) { number -> Text( modifier = Modifier.padding(vertical = 30.dp), text = "내 번호: $number" ) } }

Slide 15

Slide 15 text

2022 찰스의 안드로이드 컨퍼런스 spacedBy LazyColumn { items(count = 50) { number -> Text( modifier = Modifier.padding(vertical = 30.dp), text = "내 번호: $number" ) } }

Slide 16

Slide 16 text

2022 찰스의 안드로이드 컨퍼런스 spacedBy LazyColumn( verticalArrangement = Arrangement.spacedBy(30.dp) ) { 
 items(count = 50) { number -> Text(text = "내 번호: $number") } }

Slide 17

Slide 17 text

2022 찰스의 안드로이드 컨퍼런스 spacedBy Column( verticalArrangement = Arrangement.spacedBy( space = 30.dp, alignment = Alignment.CenterVertically ) ) { repeat(50) { number -> Text(text = "내 번호: $number") } }

Slide 18

Slide 18 text

2022 찰스의 안드로이드 컨퍼런스 LazyList contentPadding LazyColumn( contentPadding = PaddingValues(vertical = 30.dp), verticalArrangement = Arrangement.spacedBy(30.dp) ) { items(count = 50) { number -> Text(text = "내 번호: $number") } }

Slide 19

Slide 19 text

2022 찰스의 안드로이드 컨퍼런스 Intrinsic measurements Column(Modifier.width(IntrinsicSize.Max)) { awesomeTexts.forEach { text -> Text( modifier = Modifier.fillMaxWidth(), text = text ) } }

Slide 20

Slide 20 text

2022 찰스의 안드로이드 컨퍼런스 Intrinsic measurements Column(Modifier.width(IntrinsicSize.Min)) { awesomeTexts.forEach { text -> Text( modifier = Modifier.fillMaxWidth(), text = text ) } }

Slide 21

Slide 21 text

2022 찰스의 안드로이드 컨퍼런스 Arrangement, Alignment Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "가운데 배치되는 텍스트 - 1") Text(text = "가운데 배치되는 텍스트 - 2") }

Slide 22

Slide 22 text

2022 찰스의 안드로이드 컨퍼런스 Arrangement, Alignment •Arrangement: 레이아웃의 중심축 방향 •Alignment: 레이아웃의 중심축 반대 방향 Row Column Arrangement Alignment

Slide 23

Slide 23 text

2022 찰스의 안드로이드 컨퍼런스 Arrangement [Arrangement.SpaceBetween] 첫 번째와 마지막 항목을 각각 맨 끝에 배치하고, 나머지 항목들을 균등하게 가운데에 배치 [Arrangement.SpaceAround] 양 쪽 끝에 동일한 간격으로 패딩을 넣어주고, 가운데에는 양 쪽 끝에 들어간 패딩보다 더 큰 패딩을 사용하여 나머지 아이템들을 균등하게 배치 [Arrangement.SpaceEvenly] 모든 아이템들에 동일한 간격의 패딩을 넣어서 균등하게 배치

Slide 24

Slide 24 text

2022 찰스의 안드로이드 컨퍼런스 Arrangement object TopWithFooter : Arrangement.Vertical { override fun Density.arrange( totalSize: Int, // 배치 가능한 전체 높이 sizes: IntArray, // 배치될 아이템들의 높이 배열 outPositions: IntArray, // 아이템들이 배치된 위치가 담길 배열 ) { var y = 0 // 배치할 y 위치 sizes.forEachIndexed { index, size -> outPositions[index] = y // 아이템 배치 y += size // y 에 배치된 아이템 높이 만큼 추가 } if (y < totalSize) { // 만약 배치 가능한 공간이 남았다면 // 전체 높이에서 마지막 아이템의 높이 만큼 뺀 위치에 마지막 아이템을 재배치 outPositions[outPositions.lastIndex] = totalSize - sizes.last() } } }

Slide 25

Slide 25 text

2022 찰스의 안드로이드 컨퍼런스 Arrangement LazyColumn(verticalArrangement = TopWithFooter) { items(count = 5) { index -> Text(text = "${index}번째 항목") } item { Text(text = "© 성빈랜드") } }

Slide 26

Slide 26 text

2022 찰스의 안드로이드 컨퍼런스 Modifier var isInvisibleState by remember { mutableStateOf(false) } Box( modifier = Modifier .size(250.dp) .noRippleClickable { isInvisibleState = !isInvisibleState } .invisible(isInvisibleState) .background(color = Color.Green) )

Slide 27

Slide 27 text

2022 찰스의 안드로이드 컨퍼런스 Standard Modifier object InvisibleModifier : DrawModifier { override fun ContentDrawScope.draw() {} } infix fun then(other: Modifier): Modifier = if (other === Modifier) this else CombinedModifier(this, other) fun Modifier.invisible(isInvisible: Boolean) = when (isInvisible) { true -> then(InvisibleModifier) else -> this }

Slide 28

Slide 28 text

2022 찰스의 안드로이드 컨퍼런스 Composed Modifier fun Modifier.composed( inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo, factory: @Composable Modifier.() -> Modifier ): Modifier = this.then(ComposedModifier(inspectorInfo, factory)) inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit) = composed { clickable( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { onClick() } ) }

Slide 29

Slide 29 text

2022 찰스의 안드로이드 컨퍼런스 Modifier var isInvisibleState by remember { mutableStateOf(false) } Box( modifier = Modifier .size(250.dp) .noRippleClickable { isInvisibleState = !isInvisibleState } .invisible(isInvisibleState) .background(color = Color.Green) )

Slide 30

Slide 30 text

2022 찰스의 안드로이드 컨퍼런스 BackHandler var isBackPressed by remember { mutableStateOf(false) } BackHandler(enabled = !isBackPressed) { isBackPressed = true } Text("뒤로가기 눌림: $isBackPressed")

Slide 31

Slide 31 text

2022 찰스의 안드로이드 컨퍼런스 2. Runtime

Slide 32

Slide 32 text

2022 찰스의 안드로이드 컨퍼런스 remember, rememberSaveable @Composable inline fun remember( vararg keys: Any?, calculation: @DisallowComposableCalls () -> T ): T @Composable fun rememberSaveable( vararg inputs: Any?, saver: Saver = autoSaver(), key: String? = null, init: () -> T ): T

Slide 33

Slide 33 text

2022 찰스의 안드로이드 컨퍼런스 rememberSaveable - key val finalKey = if (!key.isNullOrEmpty()) { key } else { // 외부에 저장된 상태를 컴포지션에 매핑하는 데 사용되는 해시 값 currentCompositeKeyHash.toString(MaxSupportedRadix) }

Slide 34

Slide 34 text

2022 찰스의 안드로이드 컨퍼런스 rememberSaveable - saver interface Saver { // 값을 번들에 저장 가능한 값으로 변환 fun SaverScope.save(value: Original): Saveable? // 번들에 저장된 값을 원래의 값으로 변환 fun restore(value: Saveable): Original? } private val AutoSaver = Saver( save = { it }, restore = { it } ) fun autoSaver(): Saver = (AutoSaver as Saver)

Slide 35

Slide 35 text

2022 찰스의 안드로이드 컨퍼런스 Saver private val AcceptableClasses = arrayOf( Serializable::class.java, Parcelable::class.java, String::class.java, SparseArray::class.java, Binder::class.java, Size::class.java, SizeF::class.java )

Slide 36

Slide 36 text

2022 찰스의 안드로이드 컨퍼런스 Saver java.lang.IllegalArgumentException: IntHolder(value=1) cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable(). data class IntHolder(var value: Int) rememberSaveable { IntHolder(1) }

Slide 37

Slide 37 text

2022 찰스의 안드로이드 컨퍼런스 Saver val IntHolderSaver = Saver( save = { intHolder -> intHolder.value }, restore = { value -> IntHolder(value) } ) rememberSaveable(saver = IntHolderSaver) { IntHolder(1) }

Slide 38

Slide 38 text

2022 찰스의 안드로이드 컨퍼런스 rememberSaveable - Parcelable @Parcelize data class IntHolder(var value: Int) : Parcelable rememberSaveable { IntHolder(1) }

Slide 39

Slide 39 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal

Slide 40

Slide 40 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal? [setContent] root composable first composable thrid composable second composable fun main() { setContent { FirstComposable() SecondComposable() ThirdComposable() } } @Composable fun FirstComposable() {} @Composable fun SecondComposable() {} @Composable fun ThirdComposable() {}

Slide 41

Slide 41 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal 생성 fun compositionLocalOf( policy: SnapshotMutationPolicy = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal = DynamicProvidableCompositionLocal(policy, defaultFactory) fun staticCompositionLocalOf(defaultFactory: () -> T): ProvidableCompositionLocal = StaticProvidableCompositionLocal(defaultFactory)

Slide 42

Slide 42 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal provides val LocalNumber = compositionLocalOf { 0 } CompositionLocalProvider(LocalNumber provides 1) { Text(LocalNumber.current.toString()) Text(LocalNumber.current.toString()) Text(LocalNumber.current.toString()) }

Slide 43

Slide 43 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal nested provides CompositionLocalProvider(LocalNumber provides 1) { Text(LocalNumber.current.toString()) CompositionLocalProvider(LocalNumber provides 2) { Text(LocalNumber.current.toString()) } Text(LocalNumber.current.toString()) }

Slide 44

Slide 44 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal providers @Stable abstract class ProvidableCompositionLocal(defaultFactory: () -> T) : CompositionLocal(defaultFactory) { infix fun provides(value: T) = ProvidedValue( compositionLocal = this, value = value, canOverride = true ) infix fun providesDefault(value: T) = ProvidedValue( compositionLocal = this, value = value, canOverride = false ) }

Slide 45

Slide 45 text

2022 찰스의 안드로이드 컨퍼런스 CompositionLocal providesDefault CompositionLocalProvider(LocalNumber provides 1) { Text(LocalNumber.current.toString()) CompositionLocalProvider(LocalNumber providesDefault 2) { Text(LocalNumber.current.toString()) } Text(LocalNumber.current.toString()) }

Slide 46

Slide 46 text

2022 찰스의 안드로이드 컨퍼런스 State - 1 fun main() { setContent { var value by remember { mutableStateOf("가") } LaunchedEffect(Unit) { delay(1000) value = "나" } ShowValue(value) } } @Composable fun ShowValue(value: Any) { val valueState by remember { mutableStateOf(value) } Text(valueState.toString()) }

Slide 47

Slide 47 text

2022 찰스의 안드로이드 컨퍼런스 State - 1 @Composable fun ShowValue(value: Any) { val valueState by remember(value) { mutableStateOf(value) } Text(valueState.toString()) } @Composable fun ShowValue(value: Any) { val valueState by remember { mutableStateOf(value) }.apply { this.value = value } Text(valueState.toString()) }

Slide 48

Slide 48 text

2022 찰스의 안드로이드 컨퍼런스 rememberUpdatedState @Composable fun rememberUpdatedState(newValue: T): State = remember { mutableStateOf(newValue) }.apply { value = newValue } @Composable fun ShowValue(value: Any) { val valueState by rememberUpdatedState(value) Text(valueState.toString()) }

Slide 49

Slide 49 text

2022 찰스의 안드로이드 컨퍼런스 State - 2 @Composable fun ItemLoad(vm: MainViewModel) { var loadState by remember { mutableStateOf(LoadState.Loading) } LaunchedEffect(vm) { loadState = vm.loadItems() } Text( text = when (loadState) { LoadState.Loading -> "Loading..." LoadState.Done -> "Done!" } ) }

Slide 50

Slide 50 text

2022 찰스의 안드로이드 컨퍼런스 produceState @Composable fun produceState( initialValue: T, producer: suspend ProduceStateScope.() -> Unit ): State { val result = remember { mutableStateOf(initialValue) } LaunchedEffect(Unit) { ProduceStateScopeImpl(result, coroutineContext).producer() } return result }

Slide 51

Slide 51 text

2022 찰스의 안드로이드 컨퍼런스 produceState @Composable fun ItemLoad(vm: MainViewModel) { val loadState by produceState(initialValue = LoadState.Loading, key1 = vm) { value = vm.loadItems() } Text( text = when (loadState) { LoadState.Loading -> "Loading..." LoadState.Done -> "Done!" } ) }

Slide 52

Slide 52 text

2022 찰스의 안드로이드 컨퍼런스 State - 3 fun main() { setContent { var isEven by remember { mutableStateOf(false) } LaunchedEffect(Unit) { delay(1000) isEven = true } NumberFilter(isEven) } } @Composable fun NumberFilter(isEven: Boolean) { val number = remember { 1..10 } val isEvenState by rememberUpdatedState(isEven) val filteredNumber by remember(number) { mutableStateOf( number.filter { it % 2 == when (isEvenState) { true -> 0 else -> 1 } } ) } Text(filteredNumber.joinToString(", ")) }

Slide 53

Slide 53 text

2022 찰스의 안드로이드 컨퍼런스 derivedStateOf fun main() { setContent { var isEven by remember { mutableStateOf(false) } LaunchedEffect(Unit) { delay(1000) isEven = true } NumberFilter(isEven) } } @Composable fun NumberFilter(isEven: Boolean) { val number = remember { 1..10 } val isEvenState by rememberUpdatedState(isEven) val filteredNumber by remember(number) { derivedStateOf { number.filter { it % 2 == when (isEvenState) { true -> 0 else -> 1 } } } } Text(filteredNumber.joinToString(", ")) }

Slide 54

Slide 54 text

2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy? fun mutableStateOf( value: T, policy: SnapshotMutationPolicy = structuralEqualityPolicy() ): MutableState = createSnapshotMutableState(value, policy) fun compositionLocalOf( policy: SnapshotMutationPolicy = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal = DynamicProvidableCompositionLocal(policy, defaultFactory)

Slide 55

Slide 55 text

2022 찰스의 안드로이드 컨퍼런스 3. Snapshot System

Slide 56

Slide 56 text

2022 찰스의 안드로이드 컨퍼런스 Snapshot System 소개 컴포지션은 특정 순서에 구애받지 않고 무작위 병렬로 실행된다. → 하나의 State 에 여러 컴포지션이 동시에 접근할 수 있으며, 교착 상태에 빠질 수 있음 동시성 제어를 위해 MultiVersion Concurrency Control 채택 → ACID 중 I 를 구현하기 위한 방법 중 하나 ACI 를 지키며 구현된 상태 시스템이 Snapshot System

Slide 57

Slide 57 text

2022 찰스의 안드로이드 컨퍼런스 ACID 데이터베이스 트랜잭션간 데이터 유효성을 보장하기 위한 속성 •원자성(Atomicity): 트랜잭션의 결과가 성공/실패 둘 중 하나로 결정돼야 한다. •일관성(Consistency): 트랜잭션을 수행하기 전과 후에 데이터베이스가 일관해야 한다. •고립성(Isolation): 각 트랜잭션은 다른 트랜잭션으로부터 자유로워야 한다. •지속성(Durablility): 트랜잭션의 결과가 영구히 저장돼야 한다. 안드로이드는 데이터를 메모리에 저장하기에 D 를 제외한 ACI 를 컴포즈 스냅샷 시스템에서 지키고 있다.

Slide 58

Slide 58 text

2022 찰스의 안드로이드 컨퍼런스 MVCC ACID 의 I 를 구현하는 방법인 동시성 제어(Concurrency Control)중 하나 read, write 작업에 lock 대신 각 트랜잭션이 진행될 때 데이터베이스 스냅샷을 캡쳐하여 
 해당 스냅샷 안에서 고립된 상태로 트랜잭션을 진행한다. age = 10 [A] age = 20 (커밋 전) [B] age == 10 undo

Slide 59

Slide 59 text

2022 찰스의 안드로이드 컨퍼런스 MVCC age = 10 [A] age = 20 (커밋 전) [B] age == 10 StateObject StateRecord undo ACID 의 I 를 구현하는 방법인 동시성 제어(Concurrency Control)중 하나 read, write 작업에 lock 대신 각 트랜잭션이 진행될 때 데이터베이스 스냅샷을 캡쳐하여 
 해당 스냅샷 안에서 고립된 상태로 트랜잭션을 진행한다.

Slide 60

Slide 60 text

2022 찰스의 안드로이드 컨퍼런스 StateObject interface StateObject { // StateRecord 의 LinkedList 시작점 val firstStateRecord: StateRecord // StateRecord 의 LinkedList 에 새 레코드 추가 fun prependStateRecord(value: StateRecord) // 스냅샷이 충돌했을 때 병합하기 위한 방법 fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord, ): StateRecord? = null }

Slide 61

Slide 61 text

2022 찰스의 안드로이드 컨퍼런스 StateRecord abstract class StateRecord { // StateRecord 가 생성된 스냅샷의 아이디 internal var snapshotId: Int = currentSnapshot().id // 다음 StateRecord internal var next: StateRecord? = null // 다른 StateRecord 에서 값 복사 abstract fun assign(value: StateRecord) // 새로운 StateRecord 생성 abstract fun create(): StateRecord }

Slide 62

Slide 62 text

2022 찰스의 안드로이드 컨퍼런스 🖨 Snapshot System 1 📸 🖨 1 1 10 🖨 🖨 12 20 fun main() { var age by mutableStateOf(1) val snap1 = Snapshot.takeMutableSnapshot() val snap2 = Snapshot.takeMutableSnapshot() println(age) // 1 snap1.enter { age = 10 age = 12 println(age) // 12 } snap2.enter { age = 20 println(age) // 20 } println(age) // 1 }

Slide 63

Slide 63 text

2022 찰스의 안드로이드 컨퍼런스 Snapshot System 1 1 1 📸 10 🖨 🖨 12 🖨 🖨 12 20 fun main() { var age by mutableStateOf(1) val snap1 = Snapshot.takeMutableSnapshot() val snap2 = Snapshot.takeMutableSnapshot() println(age) // 1 snap1.enter { age = 10 age = 12 println(age) // 12 } snap1.apply() snap1.dispose() snap2.enter { age = 20 println(age) // 20 } println(age) // 12 }

Slide 64

Slide 64 text

2022 찰스의 안드로이드 컨퍼런스 Snapshot System 1 1 🖨 🖨 🖨 20 1 📸 10 🖨 12 12 fun main() { var age by mutableStateOf(1) // ... snap1.enter { age = 10 age = 12 println(age) // 12 } snap1.apply() snap1.dispose() snap2.enter { age = 20 println(age) // 20 } snap2.apply() snap2.dispose() println(age) // 12 }

Slide 65

Slide 65 text

2022 찰스의 안드로이드 컨퍼런스 mergeRecords // 스냅샷이 충돌했을 때 병합하는 방법 fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord, ): StateRecord? = null

Slide 66

Slide 66 text

2022 찰스의 안드로이드 컨퍼런스 policy fun mutableStateOf( value: T, policy: SnapshotMutationPolicy = structuralEqualityPolicy() ): MutableState = createSnapshotMutableState(value, policy) fun compositionLocalOf( policy: SnapshotMutationPolicy = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal = DynamicProvidableCompositionLocal(policy, defaultFactory)

Slide 67

Slide 67 text

2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy interface SnapshotMutationPolicy { // a: 이전 StateRecord 값 // b: 새로 만들려고 하는 StateRecord 값 // 새로운 StateRecord 값과 이전 StateRecord 값의 일치 비교 // 값이 다를때만 새로운 StateRecord 를 생성한다. fun equivalent(a: T, b: T): Boolean // previous: 스냅샷을 캡쳐했을 당시의 StateRecord 값 // current: 현재의 StateRecord 값 // applied: 적용하려고 한 StateRecord 값 // 스냅샷이 충돌했을 때 병합하는 방법 fun merge(previous: T, current: T, applied: T): T? = null }

Slide 68

Slide 68 text

2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy object ReferentialEqualityPolicy : SnapshotMutationPolicy { override fun equivalent(a: Any?, b: Any?) = a === b } object StructuralEqualityPolicy : SnapshotMutationPolicy { override fun equivalent(a: Any?, b: Any?) = a == b } object NeverEqualPolicy : SnapshotMutationPolicy { override fun equivalent(a: Any?, b: Any?) = false }

Slide 69

Slide 69 text

2022 찰스의 안드로이드 컨퍼런스 Snapshot System 1 🖨 20 📸 12 1 1 10 12 11220 merge! fun main() { var age by mutableStateOf( value = 1, policy = object : SnapshotMutationPolicy { override fun equivalent(a: Int, b: Int) = a == b override fun merge(previous: Int, current: Int, applied: Int) = "$previous$current$applied".toInt() } ) 
 // ... snap1.enter { age = 10 age = 12 } snap1.apply() snap2.enter { age = 20 } snap2.apply() println(age) // 11220 (previous: 1, current: 12, applied: 20) }

Slide 70

Slide 70 text

2022 찰스의 안드로이드 컨퍼런스 Snapshot 의문점 🤔 1. 명시적 스냅샷 생성 없이 바로 StateObject 접근하면 StateRecord 는 어디에 쌓이지? 2. StateObject 변경점을 어떻게 감지하고 리컴포지션을 하지?

Slide 71

Slide 71 text

2022 찰스의 안드로이드 컨퍼런스 1. 스냅샷 없이 StateObject 접근 fun main() { var age by mutableStateOf(1) age = 21 }

Slide 72

Slide 72 text

2022 찰스의 안드로이드 컨퍼런스 Global Global Snapshot 1 1 10 12 20 1 📸

Slide 73

Slide 73 text

2022 찰스의 안드로이드 컨퍼런스 Global Global Snapshot 1 1 10 12 20 1 📸 “advanced”

Slide 74

Slide 74 text

2022 찰스의 안드로이드 컨퍼런스 2. Global Snapshot 변경점 감지 internal object GlobalSnapshotManager { private val started = AtomicBoolean(false) fun ensureStarted() { if (started.compareAndSet(false, true)) { val channel = Channel(Channel.CONFLATED) CoroutineScope(AndroidUiDispatcher.Main).launch { channel.consumeEach { Snapshot.sendApplyNotifications() // advanced! } } Snapshot.registerGlobalWriteObserver { channel.trySend(Unit) } } } }

Slide 75

Slide 75 text

2022 찰스의 안드로이드 컨퍼런스 2. 일반 스냅샷 변경점 감지 fun takeMutableSnapshot( readObserver: ((Any) -> Unit)? = null, writeObserver: ((Any) -> Unit)? = null ): MutableSnapshot

Slide 76

Slide 76 text

2022 찰스의 안드로이드 컨퍼런스 Composing Snapshot private inline fun composing( composition: ControlledComposition, modifiedValues: IdentityArraySet?, block: () -> T ): T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } }

Slide 77

Slide 77 text

2022 찰스의 안드로이드 컨퍼런스 Composing Snapshot 📸 1 1 10 12 20 1 📸 Composing Global

Slide 78

Slide 78 text

2022 찰스의 안드로이드 컨퍼런스 4. LiveLiterals

Slide 79

Slide 79 text

2022 찰스의 안드로이드 컨퍼런스 LiveLiterals

Slide 80

Slide 80 text

2022 찰스의 안드로이드 컨퍼런스 @NoLiveLiterals @NoLiveLiterals @Composable fun Foo() { Text("Bar!") }

Slide 81

Slide 81 text

2022 찰스의 안드로이드 컨퍼런스 @file:NoLiveLiterals @file:NoLiveLiterals package land.sungbin.androidplayground class MainFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // ... } }

Slide 82

Slide 82 text

2022 찰스의 안드로이드 컨퍼런스 준비했지만 다루지 못한 내용

Slide 83

Slide 83 text

2022 찰스의 안드로이드 컨퍼런스 추가 내용: 🏰 sungbin.land

Slide 84

Slide 84 text

2022 찰스의 안드로이드 컨퍼런스 Fun Fact

Slide 85

Slide 85 text

2022 찰스의 안드로이드 컨퍼런스 끝! 감사합니다 🙇