작성 무엇을 해야하는지를 작성 작업 완료에 필요한 모든 단계를 명시적으로 정의 작업 완료에 필요한 논리적 구조를 추상화 데이터 할당, 계산, 반복문, 조건문 같은 연산을 포함 함수와 타입으로 개념적 관계를 표현 컴퓨터가 명령을 실행하는 방식을 추상화 비즈니스 규칙, 데이터 관계, 상태 전이에 집중 13
02 아이디어가 이미 존재하는지 확인 (선택) 좋은 협업자 찾기 Android와 Compose Multiplatform용 햅틱 라이브러리 혼자 작업하면 API 설계와 프로젝트 비전이 지나치게 한쪽 는 이미 여럿 있었지만, Compose Runtime 기반이거나 선 으로 치우칠 수 있음 언형 API를 제공하는 것은 없었음 좋은 협업자는 다른 관점을 더하고 프로젝트의 균형을 유지 하도록 도와줌 16
첫 stable 릴리스를 발표한 후 피드백을 받음 개발자들은 Compose Runtime 기반 API만이 아닌 순수 Kotlin DSL을 원함 기존 설계를 재검토 처음에는 학습 곡선을 낮추기 위해 Compose Runtime을 선택 하지만 Kotlin DSL로도 비슷하게 간결하고 표현력 있는 API를 제공할 수 있음 라이브러리를 재구성 Kotlin 기반 API를 핵심 구현으로 삼음 Compose나 Compose Multiplatform을 사용하지 않는 개발자까지 지 원을 확장 17
HapticElement { val children: MutableList<HapticElement> fun collectEvents(startTimeMs: Long): List<ScheduledHapticEvent> fun totalDurationMs(startTimeMs: Long): Long } children 햅틱 패턴을 이루는 자식 노드 collectEvents 트리를 플랫폼 API가 소비할 수 있는 리스트로 평탄화 totalDurationMs “내가 끝났을 때 얼마나 시간이 지났는가”에 답함 부모는 이를 이용해 다음 형제의 시작 시점을 스케줄링 25
어떤 @Composable이 어떤 노드를 어떤 순서 로 방출했는지 추적 컴파일러가 추적 코드를 추가 Composition 무엇이 어디에 방출되었는지 기억 Diff: 지난번 이후 바뀐 것 재실행 시 이전 상태와 비교(diff)해 최소한의 변 경을 계산 노드 타입은 제네릭으로 정의 Applier 트리를 직접 다루는 유일한 부분 노드 트리 32
어떻게 바꿀지를 결정 internal class JindongApplier( root: HapticElement, ) : AbstractApplier<HapticElement>(root) { override fun insertTopDown(index: Int, instance: HapticElement) { current.children.add(index, instance) } override fun remove(index: Int, count: Int) { current.children.subList(index, index + count).clear() } 삽입, 제거, 이동 — 모두 children만 건드 림 current - 런타임이 트리에서 현재 위 치한 곳, AbstractApplier가 추적 Compose UI에도 하나 있음 — 대신 LayoutNode를 삽입 override fun move(from: Int, to: Int, count: Int) { if (from == to) return val items = current.children.subList(from, from + count).toList() current.children.subList(from, from + count).clear() current.children.addAll(to, items) } } 33
하나 @Composable fun JindongScope.Haptic(duration: Duration, intensity: HapticIntensity = MEDIUM) { ComposeNode<VibrationElement, JindongApplier>( factory = { VibrationElement(duration.inWholeMilliseconds, intensity) }, update = { }, ) } 두 개의 타입 파라미터: 무엇을 생성할지 + 누가 삽입할지 Composition(applier, recomposer) - 우리의 Applier가 주 입되는 곳 private class JindongCompositionHost { private val rootElement = SequenceElement() private val applier = JindongApplier(rootElement) private val recomposer = Recomposer(clock) private val composition = Composition(applier, recomposer) } 34
삽입 Core가 트리를 정의 — Compose는 어디에도 없음 Applier 덕분에 Compose Runtime이 같은 트 리를 만들 수 있음 두 DSL 모두 결국 같은 이벤트 리스트에 도달 HapticElement 트리 collectEvents → List<ScheduledHapticEvent> HapticExecutor 햅틱 피드백 36