안드로이드 아키텍처 용어 사전
데이터offline-first

오프라인 우선

로컬 DB를 진실의 원천으로 삼고 네트워크는 그것을 갱신만 하는 구조.

로컬 DB를 진실의 원천으로 두고, 네트워크는 그것을 갱신만 하는 구조.

화면 ──관찰──→ Room ←──갱신── 네트워크

화면은 네트워크를 모른다. Room 만 본다

무엇이 좋아지나

  • 앱을 켜자마자 데이터가 보인다 (스피너 없이)
  • 지하철·엘리베이터에서도 마지막 데이터가 남는다
  • 네트워크 실패가 화면을 비우지 않는다
  • 여러 화면이 같은 Room 을 보므로 자동으로 동기화된다
override fun observeUsers(): Flow<List<User>> =
    dao.observeAll().map { it.map(UserEntity::toDomain) }

override suspend fun refresh(): Result<Unit> = runCatching {
    val remote = api.getUsers()
    dao.upsertAll(remote.map { it.toEntity() })   // 화면은 Flow 로 자동 갱신된다
}

쓰기는 어떻게 하나 — 낙관적 갱신

suspend fun toggleBookmark(id: Long) {
    dao.setBookmarked(id, true)                       // ① 즉시 로컬 반영 → 화면이 바로 바뀐다
    runCatching { api.bookmark(id) }
        .onFailure { dao.setBookmarked(id, false) }   // ② 실패하면 되돌린다
}

더 견고하게 하려면 '동기화 대기열' 을 둔다

  • 로컬에 pending 플래그로 기록 → WorkManager 가 네트워크가 살아날 때 전송
  • 앱을 꺼도 살아남는다

충돌을 어떻게 푸나

  • last-write-wins — 타임스탬프가 늦은 쪽이 이긴다. 가장 단순. 데이터를 잃을 수 있다

  • 서버 우선 — 서버 값으로 덮는다. 로컬 편집이 있으면 위험

  • 필드 병합 — 필드별로 최신 값을 취한다. 구현이 무겁다

  • 대부분의 앱은 '읽기는 오프라인 우선, 쓰기는 서버 확인 후' 로 충분하다

무엇을 캐시하지 말아야 하나

  • 실시간성이 본질인 데이터 (호가 · 잔여 좌석)

  • 민감 정보 (평문 토큰 · 결제 수단)

  • 자주 무효화되는 대용량 데이터

  • 오래된 데이터를 보여줄 땐 "n분 전 기준" 을 화면에 적는 것이 정직하다

면접 함정

  • "오프라인 우선 = 오프라인에서도 동작" → 온라인 상태의 체감 속도와 안정성이 주된 이득이다.
  • "메모리 캐시로 충분하다" → 프로세스가 죽으면 사라진다. 앱을 다시 켰을 때가 진짜 문제다.

WorkManager로 동기화를 미룬다

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
    .build()
WorkManager.getInstance(ctx).enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)

WorkManager 를 쓰는 이유

  • 앱이 죽어도 예약이 남는다 (내부 DB 에 기록된다)
  • 재부팅을 넘긴다
  • 네트워크가 연결될 때까지 시스템이 알아서 기다린다
  • 지수 백오프로 재시도한다

viewModelScope.launch 로 동기화하면 화면을 떠나는 순간 취소된다

화면에 무엇을 보여 주나

  • 데이터가 있고 갱신 중 — 옛 데이터 + 상단에 얇은 진행 표시

  • 데이터가 없고 갱신 중 — 스켈레톤 또는 스피너

  • 데이터가 있고 갱신 실패 — 옛 데이터 + "오프라인 — 3분 전 기준" 배너

  • 데이터가 없고 갱신 실패 — 빈 화면 + 재시도 버튼

  • 네 가지가 다르다. isLoading Boolean 하나로는 표현되지 않는다

  • UiState 를 sealed 로 나누거나 필드를 분리해야 하는 실제 이유

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Repository·UseCase — 단일 출처·비즈니스 캡슐화