먼저 Navigation을 설명하기 전에,
Jetpack Compose의 화면 전환에 대한 이해가 조금 필요하다.
Compose는 Activity가 하나만 있고, Single Activity안에서 Navigation을 통해 화면을 전환한다.
화면은 @Composable 함수로 구현되고 갱신은 Recomposition을 통해 이루어진다. 상태는 ViewModel, State로 관리한다.
blue book Recomposition은 상태가 변경될 때, UI 갱신하는 것을 의미한다.
다른 웹프레임워크 처럼, 상태변수 값이 변경 되면, 관련 된 UI가 갱신 된다.
사용법
먼저 build.gradle.kt(app) > dependencies에 다음을 추가해준다
implementation("androidx.navigation:navigation-compose:2.9.3")
그리고 MainActivity.kt 파일에 다음과 같이 작성한다
아래는 전체적인 예제 코드이다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "first",
) {
composable("first") {
FirstScreen(navController)
}
composable("second") {
SecondScreen(navController)
}
composable("third/{value}") { backStackEntry ->
ThirdScreen(navController = navController, value = backStackEntry.arguments?.getString("value") ?: "")
}
}
}
}
}
@Composable
fun FirstScreen(navController: NavController) {
val (value, setValue) = remember {
mutableStateOf("")
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("첫 화면")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
navController.navigate("second")
}) {
Text("두 번째")
}
Spacer(modifier = Modifier.height(16.dp))
TextField(value = value, onValueChange = setValue)
Button(onClick = {
if (value.isNotEmpty()) {
navController.navigate("third/$value")
}
}) {
Text("세 번째")
}
}
}
@Composable
fun SecondScreen(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("두 번째 화면")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
navController.navigateUp()
// navController.popBackStack()
}) {
Text("뒤로가기")
}
}
}
@Composable
fun ThirdScreen(navController: NavController, value: String) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("세 번째 화면")
Spacer(modifier = Modifier.height(16.dp))
Text(value)
Button(onClick = {
navController.navigateUp()
}) {
Text("뒤로가기")
}
}
}
MainActivity의 setContent 안에 있는 내용이 Navigation에 대한 설정이다.
NavHost > startDestination은 가장 먼저 보여줄 화면 route name을 입력해준다.
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "first",
) {
composable("first") {
FirstScreen(navController)
}
composable("second") {
SecondScreen(navController)
}
composable("third/{value}") { backStackEntry ->
ThirdScreen(navController = navController, value = backStackEntry.arguments?.getString("value") ?: "")
}
}
그리고 third 라는 name을 가진 route에는 backStackEntry가 사용된 것을 볼 수 있는데, 이거는 화면이동을 하면서 쌓이는 한 화면에 대한 정보 객체라고 보면 된다. 그래서 위와 같이 파라미터가 있을 경우 해당 객체에서 꺼내다가 쓰면 된다.
쌓이는 방식은 화면 이동 시에는 push, 뒤로 가기를 했을 경우에는 pop을 하는 식으로 관리된다.
다음으로 화면 전환을 하려면 기본적으로 화면(@Composable 함수)에서 NavController를 가지고 있거나, Navigation 설정에서 화면 이동에 대한 함수를 파라미터로 전달해주고, 해당 함수를 화면에서 실행해주면 된다.
아래에서는 NavController를 전달해주는 방식으로 구현했다.
@Composable
fun FirstScreen(navController: NavController) {
val (value, setValue) = remember {
mutableStateOf("")
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("첫 화면")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
navController.navigate("second")
}) {
Text("두 번째")
}
Spacer(modifier = Modifier.height(16.dp))
TextField(value = value, onValueChange = setValue)
Button(onClick = {
if (value.isNotEmpty()) {
navController.navigate("third/$value")
}
}) {
Text("세 번째")
}
}
}
세번째 화면 이동할 때 파라미터가 전달되는 것을 볼 수 있는데, 아까 위에서 설정한 세번째 화면 라우트 정보를 다시 한번 보자
composable("third/{value}") { backStackEntry ->
ThirdScreen(navController = navController, value = backStackEntry.arguments?.getString("value") ?: "")
}
보면 composable에 라우트 경로가 작성되어 있는 것을 볼수 있는데, 이러한 형태로 전달을 해주면, backStackEntry > arguments로 전달되어 온다. 그래서 화면에 해당 값을 꺼내서 전달해주면 된다.
다음으로 뒤로 가기를 하는 방법이다.
@Composable
fun SecondScreen(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("두 번째 화면")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
navController.navigateUp()
// navController.popBackStack() -> 뒤로가기를 하는 다른 방법
}) {
Text("뒤로가기")
}
}
}
보면 두가지 방법이 있는데, 똑같은 뒤로가기 지만, 뒤로가기를 할 화면이 없을때의 동작이 다르다.
navigateUp의 경우는, 더 이상 뒤로 갈 화면이 없다면, 앱 자체를 이탈한다.
popBackStack의 경우는, false를 반환하고 현재화면을 유지한다.
보통은 navigateUp 사용을 권장하고 있다. 외부앱이나 푸시를 통해서 앱에 접근할 경우를 생각해보면 일반적으로는 navigateUp을 사용하는 것이 적절할 것 같다는 생각이다.
만약에 popBackStack을 사용해서 해당 상황에 다른 로직을 추가한다면, 크게 상관은 없을 것이다.
마무리
NavController를 전역적으로 사용할 수 없다는게 조금 아쉬운 부분이긴 하다.
전역적으로 사용하려고 찾아봤는데, 전역적으로 사용하다보면 특정상황(다크모드, 화면회전)에 의해 NavController가 여러개 생성될 수 있다고 하며, 그렇게 되면 화면 흐름이 꼬이게 될 수 있다고 한다.
일단 권장하는 방법이 NavController를 하위로 내려주는 방식을 권장한다고 하니, 이대로 사용해보자
'Language \ Framework > Kotlin(Android)' 카테고리의 다른 글
| [Android] ViewModel 간단 정리 (0) | 2025.11.27 |
|---|---|
| [Android] Jetpack Compose 기본 컴포넌트 (0) | 2025.11.23 |
| [Kotlin] 기본 문법 예제 (0) | 2025.11.23 |
| [Kotlin] 기본 문법 비교 (vs Java) (0) | 2025.11.23 |