Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Deep Dive into Jetpack Compose State

Deep Dive into Jetpack Compose State

Ji Sungbin

May 22, 2023
Tweet

More Decks by Ji Sungbin

Other Decks in Technology

Transcript

  1. 2023 찰스의 안드로이드 컨퍼런스 목표 •스냅샷 시스템의 컨셉을 이해한다. •StateObject와

    StateRecord를 이해한다. •Undo/Redo 시스템을 만들 수 있다. •발표의 이해도를 높이기 위해 목표와 무관한 내용은 전체 생략
  2. 2023 찰스의 안드로이드 컨퍼런스 📸 Snapshot System •컴포즈에서 두 번째로

    깊은 계층 •State의 구현체 •리컴포지션의 뼈대
  3. 2023 찰스의 안드로이드 컨퍼런스 이걸 왜 알아야 해? •내부 개념인

    만큼 컴포즈를 사용한다고 알아야 할 필요는 없다. •컴포즈 런타임 구성으로만 쓰이기엔 아까운 기술이고, 대부분 public api로 
 구현돼 있어서 컴포즈 내부에 관심이 있다면 한 번쯤 봐볼 가치는 있다. •이해하고 있으면 컴포즈의 상태 시스템을 자유자제로 다룰 수 있다.
  4. 2023 찰스의 안드로이드 컨퍼런스 📸 Snapshot System 컨셉 컴포지션은 특정

    순서에 구애받지 않고 무작위 병렬로 실행된다. → 하나의 State에 여러 컴포지션이 동시에 접근할 수 있으며, 동시성 문제에 빠질 수 있음
  5. 2023 찰스의 안드로이드 컨퍼런스 📸 Snapshot System 컨셉 💡 모든

    State 연산을 고립되게 진행하자! 컴포지션은 특정 순서에 구애받지 않고 무작위 병렬로 실행된다. → 하나의 State에 여러 컴포지션이 동시에 접근할 수 있으며, 동시성 문제에 빠질 수 있음
  6. class StringPrinter { private var value: String? = null fun

    delayedPrint(value: String, delay: Long = 1000L) { this.value = value Thread.sleep(delay) println(this.value) } }
  7. fun main() { val stringPrinter = StringPrinter() thread { stringPrinter.delayedPrint("A")

    } thread { stringPrinter.delayedPrint("B") } } // result: B, B
  8. class StringPrinter { private val value = ThreadLocal<String>() fun delayedPrint(value:

    String, delay: Long = 1000L) { this.value.set(value) Thread.sleep(delay) println(this.value.get()) } } // result: A, B
  9. class StringPrinter { private val value = ThreadLocal<String>() fun delayedPrint(value:

    String, delay: Long = 1000L) { this.value.set(value) Thread.sleep(delay) println(this.value.get()) } } // result: A, B Snapshot
  10. class StringPrinter { private val value = ThreadLocal<String>() fun delayedPrint(value:

    String, delay: Long = 1000L) { this.value.set(value) Thread.sleep(delay) println(this.value.get()) } } // result: A, B Snapshot “Snapshot System”
  11. @Composable fun main(state: MutableState<Int>) { // 0 state.value = 1

    state.value = 2 // recomposition! } 0 📸 1 2
  12. @Composable fun main(state: MutableState<Int>) { // 0 -> 10 state.value

    = 1 state.value = 2 
 // ৻ࠗ੄ ৔ೱਵ۽ state ਗࠄ ч੉ // زदী ߄Պ } 0 📸 1 2 10
  13. @Composable fun main(state: MutableState<Int>) { // 0 -> 10 state.value

    = 1 state.value = 2 
 // recomposition! } 0 📸 1 2 10
  14. class StringPrinter { private val value = ThreadLocal<String>() fun delayedPrint(value:

    String, delay: Long = 1000L) { this.value.set(value) Thread.sleep(delay) println(this.value.get()) } } ` StateObject StateRecord
  15. val state = mutableStateOf(0) state.value = 1 state.value = 2

    state.value = 3 ` StateObject StateRecord
  16. StateRecord // mutableೠ о੢ ୭न ۨ௏٘ী ੘স ࣻ೯ fun <R>

    writable( state: StateObject, block: StateRecord.() -> R, ): R
  17. 2023 찰스의 안드로이드 컨퍼런스 Undo/Redo 시스템 1. 상태가 변경될 때마다

    새로운 상태 값 저장 2. Undo 요청 시 이전 값으로 상태 복원 3. Redo 요청 시 다음 값으로 상태 복원
  18. // അ੤ ೐ۨ੐ ੋؙझ var currentFrame = 0 // пп

    ೐ۨ੐߹ ࢚క ч(StateRecord) val frames = mutableListOf<StateRecord>()
  19. // ୶੸ ઺ੋ ࢚క੄ ч(StateRecord)ਸ ೐ۨ੐ ߓৌী ੷੢ fun saveFrame()

    { frames += target!!.copyCurrentRecord() currentFrame++ }
  20. // ઱য૓ ࢚కী ೡ׼ػ ч(StateRecord)ਸ ࠂࢎೞৈ ߈ജ fun StateObject.copyCurrentRecord(): StateRecord

    { val newRecord = firstStateRecord.create() firstStateRecord.withCurrent { current -> newRecord.assign(current) } return newRecord }
  21. // ઱য૓ State੄ ࢚క ߸҃ਸ ୶੸ೞب۾ ૑੿ fun <T> MutableState<T>.track():

    MutableState<T> { target = this as StateObject return this }
  22. 2023 찰스의 안드로이드 컨퍼런스 Undo/Redo 시스템 1. 상태가 변경될 때마다

    새로운 상태 값 저장 2. Undo 요청 시 이전 값으로 상태 복원 3. Redo 요청 시 다음 값으로 상태 복원
  23. // ୶੸ ઺ੋ ࢚క੄ чਸ ੉੹ ೐ۨ੐ਵ۽ ࠂਗ fun undo()

    { if (currentFrame - 1 in frames.indices) { target!!.restoreFrom(frames[--currentFrame]) } }
  24. // ୶੸ ઺ੋ ࢚క੄ чਸ ׮਺ ೐ۨ੐ਵ۽ ࠂਗ fun redo()

    { if (currentFrame + 1 in frames.indices) { target!!.restoreFrom(frames[++currentFrame]) } }
  25. 2023 찰스의 안드로이드 컨퍼런스 Undo/Redo 시스템 1. 상태가 변경될 때마다

    새로운 상태 값 저장 2. Undo 요청 시 이전 값으로 상태 복원 3. Redo 요청 시 다음 값으로 상태 복원
  26. 2023 찰스의 안드로이드 컨퍼런스 지금까지 만든 Undo/Redo 시스템 1. saveFrame():

    현재 프레임의 상태 값(StateRecord) 저장 2. track(): 추적할 상태(StateObject) 지정 3. undo(): 이전 프레임으로 상태 값(StateRecord) 복원 4. redo(): 다음 프레임으로 상태 값(StateRecord) 복원
  27. 2023 찰스의 안드로이드 컨퍼런스 이제 만들 Undo/Redo 시스템 1. saveFrame()

    호출 시점 2. 과거 프레임에서 신규 프레임을 저장했을 때 프레임 처리 정책
  28. 2023 찰스의 안드로이드 컨퍼런스 saveFrame() 호출 시점 • 추적 중인

    상태(StateObject)에 값(StateRecord)이 작성됐을 때 🙆 • undo()와 redo()로 상태 값(StateRecord)이 복원됐을 때 🙅
  29. // StateObjectী StateRecordо ੘ࢿؼ ٸ ഐ୹غח ௒ߔ ١۾ // Set<Any>:

    ੘ࢿػ StateObject ݽ਺ context(Snapshot) fun registerApplyObserver( observer: (Set<Any>, Snapshot) -> Unit, ): ObserverHandle
  30. // ୶੸ ઺ੋ ࢚కо ߸ೡ ٸ݃׮ saveFrame() ഐ୹ द੘ fun

    startRecording() { handle = Snapshot.registerApplyObserver { states, _ -> if (states.any { it == target }) { saveFrame() } } }
  31. // ઱য૓ State੄ ࢚క ߸҃ਸ ୶੸ೞب۾ ૑੿ fun <T> MutableState<T>.track():

    MutableState<T> { target = this as StateObject startRecording() return this }
  32. 2023 찰스의 안드로이드 컨퍼런스 saveFrame() 호출 시점 • 추적 중인

    상태(StateObject)에 값(StateRecord)이 작성됐을 때 🙆 • undo()와 redo()로 상태 값(StateRecord)이 복원됐을 때 🙅
  33. // ୶੸ ઺ੋ ࢚క੄ чਸ ੉੹ ೐ۨ੐ਵ۽ ࠂਗ fun undo()

    { stopRecording() if (currentFrame - 1 in frames.indices) { target!!.restoreFrom(frames[--currentFrame]) } startRecording() }
  34. // ୶੸ ઺ੋ ࢚క੄ чਸ ׮਺ ೐ۨ੐ਵ۽ ࠂਗ fun redo()

    { stopRecording() if (currentFrame + 1 in frames.indices) { target!!.restoreFrom(frames[++currentFrame]) } startRecording() }
  35. 2023 찰스의 안드로이드 컨퍼런스 이제 만들 Undo/Redo 시스템 1. saveFrame()

    호출 시점 2. 과거 프레임에서 신규 프레임을 저장했을 때 프레임 처리 정책
  36. 2023 찰스의 안드로이드 컨퍼런스 프레임 처리 정책 과거 프레임에서 신규

    프레임을 저장하면 
 과거와 현재 사이의 모든 프레임 제거 후 신규 프레임 저장
  37. // ୶੸ ઺ੋ ࢚క੄ ч(StateRecord)ਸ ೐ۨ੐ ݾ۾ী ੷੢ fun saveFrame()

    { if (currentFrame < frames.lastIndex) { frames.removeRange(start = currentFrame + 1, 
 end = frames.size) } frames += target!!.copyCurrentRecord() currentFrame++ }
  38. var currentFrame = 0 val frames = mutableListOf<StateRecord>() var target:

    StateObject? = null var handle: ObserverHandle? = null
  39. fun saveFrame() { if (currentFrame < frames.lastIndex) { frames.removeRange(currentFrame +

    1, frames.size) } frames += target!!.copyCurrentRecord() currentFrame++ }
  40. fun startRecording() { handle = Snapshot.registerApplyObserver { states, _ ->

    if (states.any { it == target }) { saveFrame() } } }
  41. fun undo() { stopRecording() if (currentFrame - 1 in frames.indices)

    { target!!.restoreFrom(frames[--currentFrame]) } startRecording() }
  42. fun redo() { stopRecording() if (currentFrame + 1 in frames.indices)

    { target!!.restoreFrom(frames[++currentFrame]) } startRecording() }
  43. // duckie-team/quack-quack-android Text( modifier = Modifier.span( texts = listOf("QuackQuack"), style

    = SpanStyle(color = Orange), ), text = "QuackQuack is an awesome ui kit.", typography = Body1, )