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

Jetpack Compose, 어디까지 알고 있을까?

Jetpack Compose, 어디까지 알고 있을까?

2022 찰스의 안드로이드 컨퍼런스에서 발표한 PPT 입니다.

미디엄에서도 확인하실 수 있습니다: https://link.medium.com/y5SWJNVhZqb

Avatar for Ji Sungbin

Ji Sungbin

June 19, 2022
Tweet

More Decks by Ji Sungbin

Other Decks in Programming

Transcript

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

    Column { Text("선택 가능한 Text - 1") Text("선택 가능한 Text - 2") } }
  2. 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") } } }
  3. 2022 찰스의 안드로이드 컨퍼런스 ClickableText val annotatedText = buildAnnotatedString {

    append("클릭 가능한 Text") } ClickableText( text = annotatedText, onClick = { offset -> toast("클릭된 오프셋: $offset") } )
  4. 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) } }
  5. 2022 찰스의 안드로이드 컨퍼런스 LazyList key LazyColumn { items(count =

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

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

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

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

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

    = 30.dp, alignment = Alignment.CenterVertically ) ) { repeat(50) { number -> Text(text = "내 번호: $number") } }
  11. 2022 찰스의 안드로이드 컨퍼런스 LazyList contentPadding LazyColumn( contentPadding = PaddingValues(vertical

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

    verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "가운데 배치되는 텍스트 - 1") Text(text = "가운데 배치되는 텍스트 - 2") }
  13. 2022 찰스의 안드로이드 컨퍼런스 Arrangement, Alignment •Arrangement: 레이아웃의 중심축 방향

    •Alignment: 레이아웃의 중심축 반대 방향 Row Column Arrangement Alignment
  14. 2022 찰스의 안드로이드 컨퍼런스 Arrangement [Arrangement.SpaceBetween] 첫 번째와 마지막 항목을

    각각 맨 끝에 배치하고, 나머지 항목들을 균등하게 가운데에 배치 [Arrangement.SpaceAround] 양 쪽 끝에 동일한 간격으로 패딩을 넣어주고, 가운데에는 양 쪽 끝에 들어간 패딩보다 더 큰 패딩을 사용하여 나머지 아이템들을 균등하게 배치 [Arrangement.SpaceEvenly] 모든 아이템들에 동일한 간격의 패딩을 넣어서 균등하게 배치
  15. 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() } } }
  16. 2022 찰스의 안드로이드 컨퍼런스 Arrangement LazyColumn(verticalArrangement = TopWithFooter) { items(count

    = 5) { index -> Text(text = "${index}번째 항목") } item { Text(text = "© 성빈랜드") } }
  17. 2022 찰스의 안드로이드 컨퍼런스 Modifier var isInvisibleState by remember {

    mutableStateOf(false) } Box( modifier = Modifier .size(250.dp) .noRippleClickable { isInvisibleState = !isInvisibleState } .invisible(isInvisibleState) .background(color = Color.Green) )
  18. 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 }
  19. 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() } ) }
  20. 2022 찰스의 안드로이드 컨퍼런스 Modifier var isInvisibleState by remember {

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

    mutableStateOf(false) } BackHandler(enabled = !isBackPressed) { isBackPressed = true } Text("뒤로가기 눌림: $isBackPressed")
  22. 2022 찰스의 안드로이드 컨퍼런스 remember, rememberSaveable @Composable inline fun <T>

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

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

    : Any> { // 값을 번들에 저장 가능한 값으로 변환 fun SaverScope.save(value: Original): Saveable? // 번들에 저장된 값을 원래의 값으로 변환 fun restore(value: Saveable): Original? } private val AutoSaver = Saver<Any?, Any>( save = { it }, restore = { it } ) fun <T> autoSaver(): Saver<T, Any> = (AutoSaver as Saver<T, Any>)
  25. 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 )
  26. 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) }
  27. 2022 찰스의 안드로이드 컨퍼런스 Saver val IntHolderSaver = Saver<IntHolder, Int>(

    save = { intHolder -> intHolder.value }, restore = { value -> IntHolder(value) } ) rememberSaveable(saver = IntHolderSaver) { IntHolder(1) }
  28. 2022 찰스의 안드로이드 컨퍼런스 rememberSaveable - Parcelable @Parcelize data class

    IntHolder(var value: Int) : Parcelable rememberSaveable { IntHolder(1) }
  29. 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() {}
  30. 2022 찰스의 안드로이드 컨퍼런스 CompositionLocal 생성 fun <T> compositionLocalOf( policy:

    SnapshotMutationPolicy<T> = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory) fun <T> staticCompositionLocalOf(defaultFactory: () -> T): ProvidableCompositionLocal<T> = StaticProvidableCompositionLocal(defaultFactory)
  31. 2022 찰스의 안드로이드 컨퍼런스 CompositionLocal provides val LocalNumber = compositionLocalOf

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

    { Text(LocalNumber.current.toString()) CompositionLocalProvider(LocalNumber provides 2) { Text(LocalNumber.current.toString()) } Text(LocalNumber.current.toString()) }
  33. 2022 찰스의 안드로이드 컨퍼런스 CompositionLocal providers @Stable abstract class ProvidableCompositionLocal<T>(defaultFactory:

    () -> T) : CompositionLocal<T>(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 ) }
  34. 2022 찰스의 안드로이드 컨퍼런스 CompositionLocal providesDefault CompositionLocalProvider(LocalNumber provides 1) {

    Text(LocalNumber.current.toString()) CompositionLocalProvider(LocalNumber providesDefault 2) { Text(LocalNumber.current.toString()) } Text(LocalNumber.current.toString()) }
  35. 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()) }
  36. 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()) }
  37. 2022 찰스의 안드로이드 컨퍼런스 rememberUpdatedState @Composable fun <T> rememberUpdatedState(newValue: T):

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

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

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

    val loadState by produceState<LoadState>(initialValue = LoadState.Loading, key1 = vm) { value = vm.loadItems() } Text( text = when (loadState) { LoadState.Loading -> "Loading..." LoadState.Done -> "Done!" } ) }
  41. 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(", ")) }
  42. 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(", ")) }
  43. 2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy? fun <T> mutableStateOf( value: T,

    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy() ): MutableState<T> = createSnapshotMutableState(value, policy) fun <T> compositionLocalOf( policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory)
  44. 2022 찰스의 안드로이드 컨퍼런스 Snapshot System 소개 컴포지션은 특정 순서에

    구애받지 않고 무작위 병렬로 실행된다. → 하나의 State 에 여러 컴포지션이 동시에 접근할 수 있으며, 교착 상태에 빠질 수 있음 동시성 제어를 위해 MultiVersion Concurrency Control 채택 → ACID 중 I 를 구현하기 위한 방법 중 하나 ACI 를 지키며 구현된 상태 시스템이 Snapshot System
  45. 2022 찰스의 안드로이드 컨퍼런스 ACID 데이터베이스 트랜잭션간 데이터 유효성을 보장하기

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

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

    = 20 (커밋 전) [B] age == 10 StateObject StateRecord undo ACID 의 I 를 구현하는 방법인 동시성 제어(Concurrency Control)중 하나 read, write 작업에 lock 대신 각 트랜잭션이 진행될 때 데이터베이스 스냅샷을 캡쳐하여 
 해당 스냅샷 안에서 고립된 상태로 트랜잭션을 진행한다.
  48. 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 }
  49. 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 }
  50. 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 }
  51. 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 }
  52. 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 }
  53. 2022 찰스의 안드로이드 컨퍼런스 mergeRecords // 스냅샷이 충돌했을 때 병합하는

    방법 fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord, ): StateRecord? = null
  54. 2022 찰스의 안드로이드 컨퍼런스 policy fun <T> mutableStateOf( value: T,

    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy() ): MutableState<T> = createSnapshotMutableState(value, policy) fun <T> compositionLocalOf( policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(), defaultFactory: () -> T ): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory)
  55. 2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy interface SnapshotMutationPolicy<T> { // 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 }
  56. 2022 찰스의 안드로이드 컨퍼런스 SnapshotMutationPolicy object ReferentialEqualityPolicy : SnapshotMutationPolicy<Any?> {

    override fun equivalent(a: Any?, b: Any?) = a === b } object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> { override fun equivalent(a: Any?, b: Any?) = a == b } object NeverEqualPolicy : SnapshotMutationPolicy<Any?> { override fun equivalent(a: Any?, b: Any?) = false }
  57. 2022 찰스의 안드로이드 컨퍼런스 Snapshot System 1 🖨 20 📸

    12 1 1 10 12 11220 merge! fun main() { var age by mutableStateOf( value = 1, policy = object : SnapshotMutationPolicy<Int> { 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) }
  58. 2022 찰스의 안드로이드 컨퍼런스 Snapshot 의문점 🤔 1. 명시적 스냅샷

    생성 없이 바로 StateObject 접근하면 StateRecord 는 어디에 쌓이지? 2. StateObject 변경점을 어떻게 감지하고 리컴포지션을 하지?
  59. 2022 찰스의 안드로이드 컨퍼런스 2. Global Snapshot 변경점 감지 internal

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

    takeMutableSnapshot( readObserver: ((Any) -> Unit)? = null, writeObserver: ((Any) -> Unit)? = null ): MutableSnapshot
  61. 2022 찰스의 안드로이드 컨퍼런스 Composing Snapshot private inline fun <T>

    composing( composition: ControlledComposition, modifiedValues: IdentityArraySet<Any>?, block: () -> T ): T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } }
  62. 2022 찰스의 안드로이드 컨퍼런스 @file:NoLiveLiterals @file:NoLiveLiterals package land.sungbin.androidplayground class MainFragment

    : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // ... } }