← Docs hub

SKIX 응용 프로그램 아키텍처 표준

먼저 이해할 내용

이 문서는 PUI와 시스템 앱을 같은 구조와 규칙으로 개발하기 위한 기준이다. 핵심은 세 영역의 역할을 섞지 않는 것이다.

표기 이 문서에서의 의미
현재 구현 현재 MR7 계열 앱에서 확인되는 직접 Binder 호출, 주기 조회와 화면 이벤트 처리
병합 후보 task_manager 브랜치의 TaskManager, 완료 근거 저장소, Task callback과 호환 Bridge
목표 계약 공개 Manager API, 정규화된 Task 상태, Repository Flow와 상태 기반 화면 갱신

아래 PUI 흐름은 목표 UX와 목표 계약이다. 현재 앱이 이미 전 과정을 구현했다는 의미가 아니다.

영역 쉬운 설명 대표 책임
화면이 하는 일 현재 상태를 보여주고 사용자의 입력을 전달한다. 청정 상태 표시, 정지 버튼 입력, 오류 안내
앱 내부가 하는 일 입력의 의미를 정리하고 화면에 필요한 상태를 만든다. ViewModel, 선택형 UseCase, Repository
제품 실행 서비스가 하는 일 요청이 가능한지 판단하고 실제 기기 동작을 끝까지 관리한다. 상태·정책·Task·실제 완료 근거
사용자 입력
  → 화면
  → 앱 내부
  → 제품 실행 서비스
  → 실제 기기 기능
  → 실행 결과
  → 앱 상태
  → 화면 갱신

화면은 기기 동작이 성공했다고 임의로 판단하지 않는다. 제품 실행 서비스가 실제 결과를 확인하고, 앱은 그 결과를 받아 화면을 바꾼다.

PUI 예시 · 청정 진행 화면

사용자가 보게 되는 일

청정 중인 화면에서 사용자가 정지 버튼을 누른다.

  1. 화면이 멈추는 중으로 바뀐다. 사용자는 버튼 입력이 전달됐다는 것을 바로 알 수 있다.
  2. 앱이 정지 요청을 제품 실행 서비스에 전달한다.
  3. 기기가 실제 청정을 멈춘다.
  4. 멈춘 결과가 앱으로 돌아오면 화면이 청정 종료 상태로 바뀐다.
정지 버튼 누름
  → 화면에 멈추는 중 표시
  → 기기가 실제 청정을 멈춤
  → 청정 종료 화면으로 변경

화면은 버튼이 눌렸다는 이유만으로 청정이 끝났다고 표시하지 않는다. 실제 기기가 멈췄다는 결과를 받은 뒤에 종료 화면으로 바꾼다.

청정 화면에서 정지 버튼을 누른 뒤 실제 기기 정지와 화면 변경까지

그림 1. 정지 버튼 입력부터 실제 청정 정지와 화면 갱신까지
원본 SVG 열기

개발자 참고 · 내부에서 연결되는 구조

사용자가 보는 단계 내부에서 맡는 곳 하는 일
정지 버튼 누름 AirClearWorkFragment 버튼 입력을 StopClicked라는 UiAction으로 전달
멈추는 중 표시 AirClearViewModel · AirClearUiState 정지 요청이 진행 중인 화면 상태 작성
실제 청정 정지 AirCleanerRepository → SkixTaskManager → DeviceAgent 청정 실행기 실행 가능 여부를 확인하고 실제 기기 기능 호출
청정 종료 화면 완료 근거 → TaskState → Repository → ViewModel 실제 정지 결과를 새 화면 상태로 변환

청정 화면과 제품 실행 책임 분리

청정 화면 책임 구조 SVG 열기

여러 정책이나 기능을 묶어야 할 때만 UseCase를 사이에 둔다. 단순 정지는 ViewModel이 Repository를 바로 호출할 수 있다. 실패 안내, 사용자 확인, 완료 뒤 이동처럼 유실되면 안 되는 결과는 UiState로 표현한다. UiEffect는 업무 결과와 무관한 짧은 애니메이션·포커스·스크롤 같은 반응에만 제한한다.

완료 결과는 어디서 만들어지는가

AirCleanerRepository가 TaskState를 새로 만드는 것은 아니다. Repository가 setAirCleanerOperation 전용 완료 listener를 붙여 청정 완료를 추측하는 구조도 아니다. 청정 기능이 남긴 물리 완료 근거를 TaskManager가 공통 Task 상태로 확정하고, 앱은 공통 Task 이벤트에서 자신이 제출한 taskId의 결과만 선택해 화면 상태로 바꾼다.

PUI 청정 정지 요청이 물리 완료 근거를 거쳐 화면 상태로 돌아오는 상세 흐름

그림 2. 요청 접수와 실제 완료를 분리한 PUI 청정 Task 왕복 구조
원본 SVG 열기

1. 정지 요청은 Task 제출 형식으로 바뀐다

현재 task_manager 브랜치에서 AirCleanerRepository.stopAirClear()는 기존과 같이 다음 요청을 만든다.

method = setAirCleanerOperation
action = STOP
mode   = 현재 청정 모드

DeviceControlResolver.resolve()가 반환한 DeviceControl은 TaskManagerCommandBridge로 감싸져 있다. Bridge는 상태 변경 명령을 다음과 같이 변환한다.

method        = submitTask
taskMethod    = setAirCleanerOperation
source        = pui
traceId       = pui-<uuid>
correlationId = traceId
action        = STOP
mode          = 현재 청정 모드

이 변환은 기존 PUI 코드가 setAirCleanerOperation을 호출하는 계약을 유지하면서, DeviceAgent 내부에서는 작업을 Task 생명주기로 관리하기 위한 호환 경계다. 조회·상태 확인 명령은 그대로 유지하고, 실제 상태를 바꾸는 명령만 TaskManager 경로로 보낸다.

TaskManagerCommandBridge의 현재 분류는 메서드 이름에 move, station, clean, schedule 등이 포함됐는지 확인하는 전환기 호환 규칙이다. 모든 상태 변경을 의미적으로 판별하는 최종 구조가 아니다. 신규 공개 API는 기능 명세와 명확한 operation ID로 Task 여부를 결정하고, 메서드 이름 추정은 기존 호출을 옮기는 기간에만 사용한다.

2. 접수 성공과 청정 완료는 다른 상태다

submitTask의 동기 응답은 작업을 접수했는지를 알려준다. TaskResult 기준으로는 accepted, taskId, state, errorCode, reason을 읽을 수 있다. 이 시점에 앱이 표현할 수 있는 상태는 요청 전달됨 또는 멈추는 중이며, 아직 청정 종료가 아니다.

구분 의미 대표 데이터 화면이 해도 되는 표현
요청 생성 사용자가 정지를 요청함 StopClicked 버튼 중복 입력 방지
Task 접수 TaskManager가 요청을 받음 accepted, taskId, QUEUED/RUNNING 멈추는 중
기능 호출 반환 기존 청정 API 호출이 반환됨 executor 반환값 완료로 표시하지 않음
물리 완료 근거 청정 Domain이 실제 정지를 관찰함 cleaning.stopped Task 결과 대기
Task 최종 상태 Platform Adapter가 내부 결과를 공개 상태로 정규화함 SUCCEEDED, FAILED, CANCELLED, TIMED_OUT 최종 화면으로 전환

API가 예외 없이 반환됐다는 사실은 명령 전달 성공에 가깝다. 모터·팬·청정 세션이 실제로 멈췄다는 뜻은 아니다. 따라서 sendModuleCommand()가 반환된 직후 청정 종료로 바꾸면 실제 기기 상태와 화면이 어긋날 수 있다.

3. 청정 Domain이 물리 완료 근거를 기록한다

TaskManager는 기존 청정 동작을 다시 구현하지 않는다. CleaningAmpTaskExecutor가 기존 setAirCleanerOperation 실행 경로를 호출하고, 완료 목표가 cleaning.stopped인 Task는 TaskCompletionWatcher를 통해 실제 완료 근거를 기다린다.

기본 청정과 이동 청정의 정지 callback인 BaseCleaningManager.onStop()에서 TaskCompletionStateStore.mark()가 cleaning.stopped를 기록한다. 이 지점은 단순 API 반환이 아니라 청정 Domain이 실제 정지를 확인한 뒤 도달하는 근거 지점이다.

BaseCleaningManager.onStop()
  → TaskCompletionStateStore.mark(
      target = cleaning.stopped,
      areaId = 현재 청정 영역,
      action = STOP
    )
  → TaskCompletionWatcher 대기 해제
  → CleaningAmpTaskExecutor 완료

완료 근거의 소유자는 청정 Domain이고, TaskManager는 그 근거를 기다려 Task 생명주기를 확정한다. PUI와 Repository는 기기 상태를 추정하거나 가짜 완료 이벤트를 만들지 않는다.

4. TaskManager가 공통 완료 이벤트를 보낸다

완료 근거가 확인되면 병합 후보 TaskManager는 내부 Task를 COMPLETED로 바꾸고 공통 onTaskEvent를 보낸다. 현재 후보 구현은 시간 초과도 FAILED와 errorCode=timeout으로 전달한다. 앱의 Platform Adapter는 이 내부 차이를 아래 공개 계약으로 정규화한다.

method     = onTaskEvent
eventName  = TASK_STATE_CHANGED
taskId     = 제출 응답에서 받은 Task 식별자
state      = SUCCEEDED | FAILED | CANCELLED | TIMED_OUT
reasonCode = timeout을 포함한 안정된 실패 사유
result     = 실행 결과

앱은 TaskManagerClientCallback.onTaskEvent()에서 이 이벤트를 받는다. 기능별 Binder listener를 계속 추가하는 대신, 앱 프로세스에 하나의 공통 Task callback을 연결하고 Platform Adapter 또는 Task Repository에서 taskId별 상태 흐름으로 나누는 편이 안전하다.

5. 앱은 Task 상태를 화면 상태로 변환한다

Task 상태와 화면 상태는 같은 객체가 아니다. Task Repository는 기기 실행 상태를 앱 계약으로 변환하고, ViewModel은 화면에 필요한 값만 AirClearUiState로 만든다.

TaskManagerClientCallback.onTaskEvent()
  → Task Platform Adapter
  → taskId로 해당 Task만 선택
  → Task Repository의 Flow<TaskState>
  → AirClearViewModel
  → AirClearUiState
  → AirClearWorkFragment
공개 Task 상태 Domain/App 해석 화면 상태
QUEUED 또는 RUNNING 정지 Task가 실행 대기 중이거나 실행 중 stopping=true, 멈추는 중
SUCCEEDED cleaning.stopped 근거까지 확인됨 running=false, 청정 종료
FAILED 실행 실패 기존 청정 상태 유지, 실패 사유와 다음 행동을 UiState에 표시
CANCELLED 정지 요청 자체가 취소됨 상태 재조회 뒤 취소 결과를 UiState에 표시
TIMED_OUT 제한 시간 내 완료 근거 없음 종료로 단정하지 않고 상태 확인 안내를 UiState에 표시

ViewModel은 SUCCEEDED를 받은 뒤에만 청정 종료 화면을 만든다. FAILED나 TIMED_OUT에서는 현재 기기 상태를 다시 조회하고, 화면에는 사용자가 이해할 수 있는 실패 이유와 다음 행동을 표시한다.

현재 AirCleanerRepository가 보강할 부분

현재 AirCleanerRepository.stopAirClear()는 sendModuleCommand()의 outdata를 로그로만 남기고, 예외가 없으면 true를 반환한다. 따라서 TaskManager가 내려준 접수 결과와 taskId를 버리지 않고 반환하는 보강이 필요하다.

현재 보강 방향 이유
Boolean로 호출 예외 유무만 반환 TaskSubmission(accepted, taskId, state, error) 반환 접수와 완료를 구분하고 후속 이벤트를 연결하기 위해 필요
정지 직후 상태 재조회 공통 onTaskEvent를 Flow<TaskState>로 변환 polling 간격 없이 실행 상태를 받기 위해 필요
기능 Repository가 Binder 세부사항까지 처리 TaskManagerClient 연결·재연결은 Platform Adapter에 격리 Feature 코드와 Service 생명주기를 분리하기 위해 필요
호출 직후 종료 이벤트 발생 가능 공개 상태 SUCCEEDED를 받은 뒤에만 청정 종료 화면 물리 완료 근거와 화면을 일치시키기 위해 필요
프로세스 재생성 시 진행 Task 연결 정보 소실 저장한 taskId로 getTaskStatus(taskId) 조회 후 관찰 재개 callback 유실 구간을 복구하기 위해 필요

AirCleanerRepository는 청정 요청과 청정 상태 접근을 제공하고, 공통 Task 관찰은 별도 Task Repository 또는 Platform Adapter가 맡는다. 두 결과는 ViewModel에서 결합한다. 이렇게 해야 청정 외의 이동·화면·TTS·측정 Task도 동일한 완료 계약을 재사용할 수 있다.

실제 코드와 목표 역할

MR7의 청정 화면은 이미 View, ViewModel, 상태, Repository로 나뉘어 있다. 목표는 새 구조로 전면 교체하는 것이 아니라 섞여 있는 책임을 단계적으로 분리하는 것이다.

현재 코드 현재 역할 목표 구조에서 보강할 점
AirClearWorkFragment 청정 상태 표시, 버튼 입력, 일부 화면 전환 사용자 입력은 UiAction, 화면에 남아야 하는 결과는 UiState로 구분
AirClearViewModel 여러 Repository 조회, AirClearUiState와 이벤트 갱신 화면 상태 작성에 집중하고 복잡한 규칙만 UseCase로 분리
AirClearUiState 청정·공기질·이동 상태 보관 불변 상태로 만들고 ViewModel만 새 상태를 작성
AirCleanerRepository Bundle 생성과 Binder 호출, 응답 변환 Binder 생명주기와 데이터 변환을 Platform Adapter로 격리
DeviceControlResolver·sendModuleCommand() DeviceAgent 연결과 명령 전달 공개 Manager·Task 계약 뒤로 감추고 앱에서 직접 다루지 않음

현재 구현은 1~4초 주기의 상태 조회가 일부 남아 있다. 가능한 영역부터 callback 또는 Flow 기반 상태 관찰로 전환하되, 실제 DeviceAgent가 제공하지 않는 이벤트를 앱이 임의로 만들어서는 안 된다.

전체 구조

앱 요청에서 제품 실행 결과까지의 역할 흐름

그림 3. 앱이 요청을 만들고 제품 실행 계층이 실제 동작을 관리하는 흐름
원본 SVG 열기

계층 하는 일 배포 형태 하지 않는 일
SKIX Application Framework 앱의 상태·입력·오류·동시성·테스트 규칙을 통일 AAR, 템플릿, 정적 검사, 테스트 도구 전역 기기 상태와 제품 정책을 소유하지 않음
SKIX Product Runtime Framework 기기 상태·정책·자원·Task·제품 UI 세션 관리 Manager API, Binder/AIDL, 플랫폼 서명 Service 화면별 렌더링과 Feature 업무 규칙을 소유하지 않음
Android / Jetpack / AOSP UI, 생명주기, 비동기 처리, 저장, IPC 제공 Android API와 Jetpack 라이브러리 SKIX 제품 의미와 물리 완료 판단을 정의하지 않음

앱은 Android/AOSP와 Jetpack의 표준 컴포넌트를 그대로 사용한다. ViewModel · StateFlow · Coroutine · Lifecycle 등을 SKIX 이름으로 다시 만들지 않는다. SKIX는 이 기술을 사용하는 방법과 계층 간 책임만 통일한다.

구성요소별 역할

화면 상태 계약

이름 쉬운 설명 예시 지켜야 할 기준
UiAction 사용자가 화면에 전달한 입력이다. 버튼 클릭, 텍스트 입력, 다시 시도 선택 화면이 상태를 직접 바꾸거나 Binder를 호출하지 않고 ViewModel 함수로 전달
UiState 화면을 다시 그리는 데 필요한 현재 정보이며, 현재 화면 전체를 표현하는 지속 상태다. 청정 상태, 위치, 풍량, 오류 표시 화면 회전·재구독 뒤에도 필요한 값만 포함
DomainEvent Task·센서·Network 등 제품 실행 계층에서 들어온 사실이다. Task 진행, 도킹 완료, 연결 변경 Repository Flow로 수신하고 화면 입력과 같은 타입으로 섞지 않음
UiEffect 유실돼도 업무 결과가 달라지지 않는 짧은 화면 반응이다. 포커스 이동, 스크롤, 강조 애니메이션 실패 안내·Dialog·권한 결정·업무 결과 Navigation을 넣지 않음

기본 흐름은 UiAction → ViewModel → UiState → View다. 외부 callback은 DomainEvent → Repository Flow → ViewModel → UiState로 흐른다. ViewModel에서 시작된 실패 안내나 Navigation 조건은 UiState에 남겨 화면 재생성 뒤에도 복구할 수 있게 한다.

앱 모듈

모듈 역할 언제 사용하는가
View UiState를 화면에 표현하고 사용자 입력을 UiAction으로 전달 모든 화면
ViewModel UiAction과 Repository 상태를 결합해 화면에 공개할 UiState를 관리 모든 화면의 상태 경계
Reducer 복잡한 상태 전이를 순수 함수로 계산 다단계 입력과 장기 진행 화면에만 선택
UseCase 하나의 사용자·제품 행동을 이름 있는 실행 단위로 묶음 여러 Repository나 업무 규칙을 한 행동으로 조합할 때 선택
StateMachine 앱 내부 Feature 상태와 허용 전이를 관리 Vital Signs 화면처럼 사용자 확인·입력·표시 상태가 복잡할 때. Task 생명주기는 복제하지 않음
Coordinator 여러 UseCase·Repository의 앱 세션과 사용자 확인 흐름을 조정 여러 화면·확인·권한 단계가 이어질 때. 기기 동작 순서와 재시도는 TaskManager에 위임
Repository 데이터와 기기 기능에 접근하는 안정된 경계 화면이나 Domain이 기기·DB·Network에 접근할 때
DataSource DB·Network·Cache·Device 중 실제 원본 하나를 처리 Repository 구현 내부
Platform Client Adapter Binder 연결과 callback을 앱 계약으로 변환 Binder 재연결, listener 해제, callback-to-Flow 변환이 필요할 때

제품 실행 모듈

모듈 역할 책임 경계
Skix*Manager 제품 실행 API의 공개 호출 창구 요청·상태 관찰·취소·결과 callback 제공. 실제 상태와 정책은 소유하지 않음
Binder Service 호출자 권한을 확인하고 요청을 내부 책임 모듈로 전달하며 연결 생명주기를 관리 상태·정책·Task의 최종 판단은 각각의 내부 책임 모듈에 맡기고 Binder 진입점에 집중시키지 않음
상태·정책·자원 계층 현재 상태에서 요청이 가능한지와 자원 충돌을 판단 실행 허용·거부 이유를 정하지만 기기 기능은 직접 수행하지 않음
TaskManager Task와 Workflow의 접수·대기·순서·진행·취소·timeout·최종 상태 관리 물리 기능은 Domain Executor에 위임
Domain Executor 이동·청정·화면·TTS·보안·측정 기능 실행 Task 생명주기와 앱 화면 상태는 소유하지 않음
Evidence Adapter 실제 시작·진행·완료·실패 근거를 TaskState와 제품 이벤트로 변환 API 접수 성공을 물리 완료로 간주하지 않음

예시 · 청정 시작부터 화면 갱신까지

1. View                 CleaningStartClicked를 UiAction으로 전달
2. ViewModel            StartCleaningUseCase 실행
3. StartCleaningUseCase 공간·모드 규칙을 확인하고 CleaningRepository 호출
4. CleaningRepository   Platform Adapter를 통해 SkixTaskManager에 요청
5. Product Runtime      정책·상태·자원을 확인하고 Domain Executor 실행
6. Evidence Adapter     청정 시작·진행·종료 근거를 TaskState로 변환
7. CleaningRepository   TaskState를 Flow로 앱에 반환
8. ViewModel            새 CleaningUiState를 만들고 View가 화면 갱신
9. 실패하면 failureMessage와 nextAction을 CleaningUiState에 기록

UseCase와 StateMachine은 필수 통과 지점이 아니다. 단순한 청정 시작 화면은 ViewModel이 CleaningRepository를 직접 호출할 수 있다.

MVVM과 단방향 상태 흐름

MVVM을 버리는 것이 아니다

MVVM은 View와 ViewModel을 나누지만, ViewModel이 어디까지 맡고 상태와 일회성 동작을 어떻게 구분할지는 충분히 정하지 않는다. SKIX는 MVVM + UDF를 기본으로 삼아 이 빈 부분을 채운다.

MVVM과 UDF 기본형, 선택형 Reducer

그림 4. MVVM·단방향 상태 흐름과 선택형 Reducer
원본 SVG 열기

interface UiState
interface UiAction
interface UiEffect

enum class CleaningNextAction {
    RETRY,
    CHECK_DEVICE_STATE,
    DISMISS,
}

data class CleaningUiState(
    val running: Boolean = false,
    val roomName: String = "",
    val progress: Int = 0,
    val failureMessage: String? = null,
    val nextAction: CleaningNextAction? = null,
) : UiState

sealed interface CleaningUiAction : UiAction {
    data object StopClicked : CleaningUiAction
}

sealed interface CleaningUiEffect : UiEffect {
    data object FocusRetryButton : CleaningUiEffect
    data object ScrollToCurrentStep : CleaningUiEffect
}

UiState, UiAction, 제한적인 UiEffect는 의미를 통일하는 계약이다. Android ViewModel, StateFlow, Coroutine과 Lifecycle은 그대로 사용하며, 얇은 선택형 BaseViewModel은 반복 코드만 줄인다.

복잡도에 따라 확장

모든 화면에 같은 수의 계층을 강제하지 않는다.

Feature 복잡도와 시스템 중심 상태 구조

그림 5. Feature 단계별 구성과 다중 Event 수렴
원본 SVG 열기

단계 호출 구조 적용 조건 예시
기본 Feature ViewModel → Repository 단일 조회·설정·상태 표시 설정값, 정보 표시
표준 Feature ViewModel → UseCase → Repository 여러 데이터나 규칙을 한 행동으로 묶음 공간 선택과 실행 가능 여부 확인
복잡한 Feature ViewModel → Reducer/UseCase → StateMachine · Coordinator → Repository 앱 화면·확인·권한의 상태 전이가 복잡함 Vital Signs, Security, Welcome UI 세션
장기 실행 진행 ViewModel → Reducer → Task Repository 단계·진행률·취소·실패 이유 표시 Task 진행, Workflow 상태

Sensor Event · Voice Event · Task Event · Network Event는 사용자 화면 입력이 아니라 제품 실행 계층에서 들어오는 DomainEvent다. 이 입력은 Repository Flow와 Product Runtime으로 모이고 ViewModel은 화면에 필요한 값만 UiState로 바꾼다. UI는 시스템 상태의 Consumer이며 시스템 상태의 주인이 아니다. 화면이 없어도 기기 작업은 유지될 수 있어야 한다.

앱 상태, 제품 UI 세션을 나타내는 UiSessionState, 기기 작업 상태인 TaskState는 서로 연결하지만 같은 상태를 중복 소유하지 않는다.

Application Framework 구성

영역 제공 요소 쉬운 역할 설명
Presentation UiState · UiAction, 제한적 UiEffect, UDF helper 화면 상태와 사용자 입력 처리 규칙
Domain UseCase · StateMachine · Coordinator 복잡한 업무 규칙과 상태 전이
Data Repository 규칙 · DataSource 규칙 · Mapper · Cache 구현 기술을 상위 계층에서 감춤
Common AppResult · AppError, 구조화 로그 · Coroutine 정책 · 테스트 도구 오류·비동기·로그·검증 방식 통일
Platform Client Adapter Manager callback 변환, Binder 재연결, 상태 투영 앱에서 Binder 생명주기 격리
UI 자산 UI Resource Kit AAR, UI Foundation AAR 공통 색상·글꼴·아이콘·화면 구성요소 제공
Testing Fake Repository, 상태·Effect 검증, 계약 테스트 로컬 테스트와 실제 Service 통합 테스트 분리

권장 모듈

skix-app-framework/
├─ architecture-contract  UiState · UiAction · UiEffect · AppResult · AppError
├─ presentation           선택형 UDF helper · 상태 기반 결과 처리 · UI 테스트 지원
├─ domain                 StateMachine · Coordinator 계약과 공통 모델
├─ common                 로그 · dispatcher 주입 · 민감정보 제거 규칙
├─ platform-client        Skix Manager adapter · Binder 재연결 · 상태 변환
├─ ui-resources           색상 · 글꼴 · 간격 · 아이콘
├─ ui-foundation          공통 View·Compose 구성요소
└─ testing                Fake · 상태 전이 · 계약 테스트 도구

Data 구현은 Feature별로 둔다. 필요하지 않은 앱이 Binder나 Room 의존성을 함께 받지 않도록 AAR을 분리한다. HAL은 앱에서 직접 호출하지 않는다.

Repository 계약

Repository 인터페이스는 상위 계층이 필요한 기능과 상태를 표현하고, Repository 구현은 Binder·Network·Room·Cache를 조합한다.

interface RobotRepository {
    val state: Flow<RobotState>
    suspend fun start(): AppResult<Unit>
    suspend fun stop(): AppResult<Unit>
}

구현은 BinderRobotRepository, NetworkRobotRepository, FakeRobotRepository처럼 교체할 수 있다. ViewModel은 Binder를 직접 호출하지 않는다.

오류·동시성·로그·테스트

항목 표준
결과 AppResult.Success<T> 또는 AppResult.Failure(AppError)
오류 Network·Timeout·Permission·Device·InvalidState·Validation·Unknown과 안정된 오류 코드
Coroutine scope 소유권, dispatcher 주입, 취소 전파, timeout과 예외 경계를 문서화
로그 traceId·sessionId·taskId 연결, 개인정보·원문·내부 예외 제거 정책 적용
단위 테스트 ViewModel의 State/Action 처리, 제한적 Effect, Reducer 전이, UseCase와 StateMachine 검증
계약 테스트 Fake Repository와 실제 Manager Adapter가 같은 결과·오류 계약을 만족하는지 확인
통합 테스트 Binder death·재연결·프로세스 복원·Task 완료 근거·UI 세션 종료 확인

AppError는 호출자가 대응할 수 있는 코드만 공개한다. 원본 Binder 예외와 기기 내부 진단 정보는 제한된 로그에 남기고 UI 문구를 오류 객체에 직접 넣지 않는다.

현재 코드에서의 전환

전면 재작성하지 않고 Feature 단위로 전환한다.

  1. 변경 가능한 UiState를 불변 상태와 새 인스턴스 갱신으로 바꾼다.
  2. Activity와 Fragment에서 Repository·Manager 직접 호출 금지를 적용한다.
  3. 사용자 입력은 UiAction, Task·Sensor·Network 입력은 Repository의 DomainEvent 흐름으로 분리한다.
  4. Binder callback·재연결·listener 해제를 Repository와 Platform Adapter로 이동한다.
  5. 실패 안내·Dialog·권한·Navigation 조건은 UiState로 표현하고, 손실 허용 UI 반응만 UiEffect로 둔다.
  6. 복잡한 화면만 선택형 Reducer·StateMachine·Coordinator를 도입하되 기기 Task 순서와 복구는 TaskManager에 맡긴다.
  7. 앱 간 화면 소유권이 필요한 기능부터 SkixUiManager와 UiSession 계약에 연결한다.

Feature 전환 완료 기준

참고 자료

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date