1. 왜 Navigation이 중요한가?
1.1 여러 화면 전환의 필수 요소
실제 앱은 단일 화면으로만 구성되지 않는다. 회원가입 화면, 메인 화면, 설정 화면 등 다양한 화면을 이동하며 데이터를 전달할 필요가 있다.
• 기존에는 Fragment나 Activity 간 전환을 위해 Intent, Bundle, FragmentManager 등을 주로 사용했다.
• Jetpack Compose에서는 Navigation Compose 라이브러리를 통해 선언형(Declarative) 방식으로 화면 전환과 인자 전달을 처리할 수 있다.
1.2 Navigation Compose의 장점
• 코드의 간결화: NavHost, NavController 등 Compose 전용 API를 통해 화면 전환 로직을 직관적으로 작성 가능.
• 인자 전달 간편화: 화면 간 데이터 전달을 navController.navigate("detail/${itemId}") 식으로 처리하면, 별도의 Intent 코드가 불필요.
• Back Stack 자동 관리: 뒤로 가기(Back) 동작이나 중첩 Navigation 같은 복잡도도 Compose가 내부적으로 처리해준다.
2. Navigation Compose 시작하기
2.1 Gradle 의존성 추가
프로젝트의 module-level build.gradle 파일에 아래와 같이 의존성을 추가한다.
dependencies {
// Jetpack Navigation Compose
implementation "androidx.navigation:navigation-compose:2.6.0"
// (Compose Core, Material 등은 이미 포함되어 있다고 가정)
}
2.2 NavHost & NavController
@Composable
fun MyAppNavHost() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToDetail = { itemId ->
navController.navigate("detail/$itemId")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) {
val itemId = it.arguments?.getString("itemId") ?: ""
DetailScreen(itemId)
}
}
}
1. rememberNavController(): Composable에서 Navigation 상태를 관리하는 NavController 객체를 생성한다.
2. NavHost: Navigation 경로를 정의해주는 컨테이너. startDestination으로 앱이 시작될 화면을 지정한다.
3. composable("home") { ... }와 composable("detail/{itemId}") { ... }: 라우트 경로(route)마다 다른 Composable 함수를 호출한다.
4. 인자 전달: navController.navigate("detail/$itemId")로 화면을 전환하고, detail/{itemId}와 같이 인자를 받을 수도 있다.
3. 아키텍처 컴포넌트와의 연동
3.1 ViewModel과 Composable
Compose에서 ViewModel을 활용해 화면 상태를 관리하면, 상태 보존과 데이터 로직 분리를 동시에 실현할 수 있다.
@Composable
fun HomeScreen(
viewModel: HomeViewModel = viewModel(),
onNavigateToDetail: (String) -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
Column {
Text(text = "현재 상태: ${uiState.status}")
uiState.items.forEach { item ->
Button(onClick = { onNavigateToDetail(item.id) }) {
Text("Go to ${item.name}")
}
}
}
}
• viewModel(): lifecycle-viewmodel-compose 라이브러리를 사용하면, Composable 함수 안에서 쉽게 ViewModel을 호출할 수 있다.
• collectAsState(): StateFlow나 LiveData 등을 Compose의 State로 변환해, 상태가 바뀔 때마다 화면이 자동으로 갱신되도록 한다.
3.2 LiveData or StateFlow?
• LiveData: 안드로이드에서 오래전부터 지원해온 관찰 가능한 데이터 홀더. 기존 MVVM 구조에 익숙하다면 사용하기 편리.
• StateFlow: 코틀린 코루틴(Flow) 기반으로, 비동기 처리와 결합하기 쉽다. Compose와도 자연스럽게 연동된다.
• 프로젝트 성격이나 팀의 합의에 따라 선택하면 되며, Compose에서는 둘 다 잘 호환된다.
4. Clean Architecture & Compose
4.1 Clean Architecture 개념 간단 정리
Clean Architecture는 의존성 방향을 “도메인(비즈니스 로직) → 데이터 → UI” 순으로 제한함으로써, 유지보수성과 테스트 용이성을 높이는 구조다.
• Domain Layer: UseCase, Entity 등 순수 비즈니스 로직
• Data Layer: Repository 구현, 네트워크/DB 접근
• UI Layer: ViewModel, Composable UI
4.2 Compose에서 Clean Architecture 적용 방법
1. UI(View) - ViewModel 분리
• Composable 함수는 UI 렌더링에 집중하고, 핵심 로직은 ViewModel이 담당한다.
2. ViewModel - UseCase 연결
• ViewModel에서 UseCase를 호출해 비즈니스 로직을 수행한다.
3. UseCase - Repository 연결
• Repository가 실제 네트워크/DB로부터 데이터를 가져온다.
4. UI 업데이트
• ViewModel이 받은 결과 데이터를 StateFlow or LiveData로 발행하면, Composable 함수가 자동으로 재구성(Recompose).
아래는 HomeViewModel과 HomeUseCase가 분리된 예시를 간단히 보자.
class HomeViewModel(private val homeUseCase: HomeUseCase) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState
init {
fetchItems()
}
private fun fetchItems() {
viewModelScope.launch {
val items = homeUseCase.getItems()
_uiState.value = HomeUiState(items = items)
}
}
}
class HomeUseCase(private val repository: HomeRepository) {
suspend fun getItems(): List<Item> {
// 복잡한 비즈니스 로직 또는 여러 Repository 결합
return repository.fetchItems()
}
}
• UI는 HomeViewModel의 uiState를 관찰하고, 화면을 갱신한다.
• ViewModel은 UseCase를 통해 데이터를 가져오고, UseCase는 Repository에 실제 로직을 위임한다.
5. 실제 예제로 합쳐보기
MainActivity에서 MyAppNavHost()를 호출해 전체 네비게이션과 화면 구성을 관리한다고 해보자.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppNavHost()
}
}
}
• HomeScreen: HomeViewModel을 주입받아 아이템 목록을 표시
• DetailScreen: 전달받은 itemId로 상세 데이터를 조회하고 보여주기
• ViewModel & UseCase & Repository: Clean Architecture 형태로 분리해 유지보수성을 높인다.
이런 구조로 구성하면, Compose UI와 Navigation, 그리고 아키텍처 컴포넌트(ViewModel, LiveData/StateFlow)들이 유기적으로 연결돼, 프로젝트가 커져도 가독성을 유지하기 쉽다.
6. 마무리
이번에는 Compose Navigation을 통한 화면 전환과 아키텍처 컴포넌트 연동 방법을 다뤄보았다.
• Navigation Compose로 라우트(Route)를 정의하고, NavController를 통해 화면 이동을 직관적으로 처리할 수 있다.
• ViewModel + StateFlow(LiveData) 조합은 선언형 UI와 찰떡궁합을 이루며, 비즈니스 로직을 깔끔하게 분리할 수 있다.
• Clean Architecture를 Compose에 적용하면, 대규모 프로젝트에서도 유지보수성과 테스트 용이성을 높일 수 있다.
'코딩' 카테고리의 다른 글
안드로이드 빌드 시스템과 Gradle 기초 (0) | 2025.01.10 |
---|---|
Compose 애니메이션, 퍼포먼스 & 테스트 (0) | 2025.01.09 |
Material Design & 테마 커스터마이징 (1) | 2025.01.05 |
Compose 상태(State)와 Recomposition, Layout 기초 (1) | 2025.01.03 |
Composable 함수와 기본 UI 컴포넌트 (0) | 2025.01.02 |