— 대규모 Android 앱에서 Compose 구조화하기

Compose를 처음 배울 때는 보통 이런 흐름으로 시작합니다.

Text()
Button()
Column()

그리고 Counter, 로그인 화면 같은 예제를 만들며:

State → Recomposition

을 이해하게 됩니다.

하지만 실제 프로젝트로 들어가면 곧 이런 고민이 생깁니다.

  • 상태(State)는 어디에 둬야 하지?
  • ViewModel은 어디까지 담당해야 하지?
  • Navigation 구조는 어떻게 가져가야 하지?
  • Flow와 Compose는 어떻게 연결해야 하지?
  • Compose와 기존 View는 어떻게 섞어야 하지?
  • 대규모 앱에서 recomposition은 어떻게 관리하지?

특히 Android 경험이 많을수록:

“Compose를 어디에 붙여야 하는가?”

보다

“Compose 시대에는 Architecture가 어떻게 바뀌는가?”

가 더 중요해집니다.

이번 글에서는 실무 Compose Architecture를 중심으로 정리해보겠습니다.


Compose Architecture의 핵심 변화

Compose의 진짜 변화는 UI Toolkit 자체보다:

Data Flow Architecture 변화

에 있습니다.


기존 Android 구조

기존 Android는 보통:

Activity
 └ Fragment
     └ View

구조였습니다.

그리고:

  • Fragment lifecycle
  • View lifecycle
  • callback
  • listener
  • adapter

중심으로 설계했습니다.


Compose 구조

Compose에서는 보통 이렇게 갑니다.

Activity
 └ NavHost
      └ Screen Composables
           └ UI Components

그리고 데이터 흐름은:

Repository
   ↓
Flow
   ↓
ViewModel
   ↓
StateFlow
   ↓
Compose UI

입니다.

즉:

Reactive State Architecture

가 중심이 됩니다.


실무에서 가장 중요한 구조

Compose 실무에서 가장 중요한 것은:

UI는 상태를 보여주기만 한다

입니다.

즉:

  • 상태(State)는 ViewModel
  • 데이터 흐름은 Flow
  • UI는 상태 렌더링

역할로 분리됩니다.


추천 구조

가장 추천되는 기본 구조는 아래입니다.

Repository
   ↓
Flow<Data>
   ↓
ViewModel
   ↓
UiState
   ↓
Compose UI

왜 이런 구조가 중요한가?

Compose는:

State 변화 → Recomposition

기반으로 동작합니다.

따라서:

상태 흐름이 명확할수록
Architecture가 단순해진다

는 특징이 있습니다.


실무 예제 구조

예를 들어 뉴스 화면.


Repository

class NewsRepository {

    fun observeArticles(): Flow<List<Article>> {
        ...
    }
}

Repository는:

  • API
  • DB
  • Cache

등 데이터 소스를 관리합니다.


ViewModel

class NewsViewModel(
    private val repository: NewsRepository
) : ViewModel() {

    val uiState =
        repository.observeArticles()
            .map {
                NewsUiState.Success(it)
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = NewsUiState.Loading
            )
}

ViewModel은:

  • 상태 보관
  • 상태 변환
  • 비즈니스 로직

을 담당합니다.


Compose UI

@Composable
fun NewsScreen(
    viewModel: NewsViewModel = viewModel()
) {

    val state by
        viewModel.uiState.collectAsStateWithLifecycle()

    when(state) {

        is Loading -> LoadingUI()

        is Success -> ArticleList()

        is Error -> ErrorUI()
    }
}

Compose는:

현재 상태를 렌더링

만 합니다.


실무 Compose에서 가장 중요한 개념

1. UiState 모델링

Compose에서는 UI 상태를 명확히 정의하는 것이 매우 중요합니다.

추천:

sealed interface UiState {

    object Loading : UiState

    data class Success(...) : UiState

    data class Error(...) : UiState
}

왜 중요한가?

이 구조를 쓰면:

현재 UI 상태가 명확해진다

는 장점이 있습니다.

Compose는 상태 기반 시스템이므로:

상태 모델링 자체가
Architecture가 된다

고 볼 수 있습니다.


2. State Hoisting

실무에서 매우 중요합니다.


좋지 않은 구조

Composable 내부에서
상태를 모두 관리

추천 구조

상태는 ViewModel
UI는 렌더링

예:

TextField(
    value = query,
    onValueChange = onQueryChanged
)

Composable은:

  • 상태를 보여주고
  • 이벤트를 위로 전달

만 합니다.


장점

이 구조는:

  • Preview 용이
  • 테스트 쉬움
  • 재사용성 증가
  • recomposition 추적 쉬움

장점이 있습니다.


3. Unidirectional Data Flow (UDF)

Compose와 가장 잘 맞는 구조입니다.


흐름

User Event
    ↑
Compose UI
    ↓
UiState
    ↓
ViewModel

핵심

State는 아래로
Event는 위로

흐릅니다.

Compose는 이 구조를 전제로 설계된 느낌에 가깝습니다.


4. Navigation Architecture

Compose 시대에는:

Single Activity Architecture

가 일반적입니다.


구조

MainActivity
 └ NavHost
      └ Screens

기존 Android와 차이

기존:

Fragment 객체 이동

Compose:

현재 route 상태 변경

입니다.


예제

NavHost(
    navController = navController,
    startDestination = "home"
)

실무 포인트

중요한 것:

  • nested graph
  • argument
  • deep link
  • back stack

입니다.


5. Flow + Compose 조합

Compose와 가장 잘 맞는 데이터 흐름은:

StateFlow

입니다.


추천 패턴

val uiState by
    viewModel.uiState.collectAsStateWithLifecycle()

collectAsStateWithLifecycle가 중요한 이유

실무에서는 lifecycle 문제가 매우 중요합니다.

추천:

collectAsState()

보다

collectAsStateWithLifecycle()

사용.


이유

Lifecycle inactive 상태에서:

  • 불필요한 collect 감소
  • 메모리 누수 감소
  • background 처리 안정성 증가

효과가 있습니다.


6. Compose와 기존 View 혼합

실무에서 매우 중요합니다.

현실적으로 대부분 앱은:

Compose Only

가 아닙니다.

실제로는:

Compose + 기존 View 혼합

구조가 훨씬 많습니다.


AndroidView

예:

AndroidView(
    factory = {
        PlayerView(it)
    }
)

자주 섞이는 것

특히:

  • ExoPlayer
  • CameraX
  • WebView
  • MapView
  • VideoView

등은 여전히 View 기반이 많습니다.


중요한 사고

Compose는:

기존 Android를 버리는 기술

이 아니라:

기존 Android 위에
선언형 UI를 얹는 구조

에 가깝습니다.


7. Recomposition 최적화

실무에서는 성능 문제가 중요합니다.


자주 발생하는 문제

불필요한 recomposition

중요한 포인트

immutable state 사용

추천:

data class UiState(...)

stable object 유지


key 지정

items(
    list,
    key = { it.id }
)

remember 사용

remember { ... }

Compose 성능의 핵심

Compose 성능 최적화의 본질은:

어떻게 redraw 할까?

보다:

어떻게 state 변화를 최소화할까?

에 가깝습니다.


실무에서 추천하는 폴더 구조

추천 예시:

ui/
 ├ screens/
 ├ components/
 ├ theme/

data/
 ├ repository/
 ├ remote/
 ├ local/

domain/
 ├ model/
 ├ usecase/

navigation/

viewmodel/

ComposeSample 프로젝트 관점

실제로 ComposeSample에서도 흐름이 잘 드러납니다.

특히:

STEP 1
→ State + Recomposition

STEP 4
→ Flow + ViewModel

STEP 5
→ Navigation

STEP 6
→ Hybrid UI

순서로:

Compose UI
→ Reactive Architecture
→ 실무 구조

로 확장되는 흐름이 매우 좋습니다.


실무에서 Compose를 잘한다는 의미

많은 사람들이:

  • fancy animation
  • custom UI
  • 디자인 시스템

을 Compose 실력이라고 생각합니다.

하지만 실제 중요한 것은:

State 설계
+
Data Flow 설계
+
Recomposition 이해
+
Reactive Architecture 이해

입니다.


Android 경험자가 특히 강점을 가지는 부분

기존 Android 경험이 깊다면:

  • Lifecycle
  • Threading
  • Multimedia
  • Navigation
  • Performance
  • Reactive stream

경험이 이미 있기 때문에:

Compose에서도 강점을 가질 가능성이 높습니다.

다만 가장 중요한 변화는:

View 중심 사고를
State 중심 사고로 바꾸는 것

입니다.


마무리

Compose 시대의 Android Architecture는 결국:

Reactive State Architecture

로 수렴합니다.

그리고 실무에서는:

Repository
→ Flow
→ ViewModel
→ StateFlow
→ Compose UI

구조가 중심이 됩니다.

Compose를 잘한다는 것은 결국:

“상태 흐름을 얼마나 명확하게 설계할 수 있는가”

에 가까워집니다.

다음 글에서는:

“Compose를 배우며 다시 생각한 Android Architecture”

를 중심으로, Compose가 Android 개발 방식 자체를 어떻게 바꾸고 있는지 조금 더 큰 관점에서 정리해보겠습니다.

+ Recent posts