๋ณธ ๊ฒ์๊ธ์์ ๋ค๋ฃฌ ์ฑ์ ์ ์ฒด ์์ค ์ฝ๋๋ #5์ ์์ต๋๋ค.
#1 ๊ฐ์
๊ธฐ์กด Android Navigation ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ "Jetpack Navigation 2"์๋ค. ๋ค์ ์ธ๋์ธ "Jetpack Navigation 3"์ด ์ถ์๋์๋๋ฐ, 2์ 3 ๊ฐ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ์ฑ์ ํ๋์ฉ ๋ง๋ค์ด๋ณธ๋ค. ๊ทธ๋ฆฌ๊ณ Navigation 3์ ๊ตฌ์กฐ๋ก๋ถํฐ (2 ๋๋น) ์ด๋ค ๊ธ์ ์ ํจ๊ณผ๋ฅผ ๊พํ๋์ง ์ดํด๋ณธ๋ค. ์ํ ์ฑ์ ์ต์ํ์ ์ฝ๋๋ก ๊ตฌ์ฑํด ํ๋์ ํ์ผ(MainActivity.kt)์ ๋๋ ค ๋ฐ์๋ค. ๊ณต๋ถ ๋ฉด์์ ํ์ผ์ ๋๋๋ ๊ฒ๋ณด๋ค ์คํ๋ ค ๋ ์ง๊ด์ ์ด๋ผ๋ ํ๋จ์์๋ค.
#2 Jetpack Navigation 2
ํ์ | App architecture | Android Developers
Android Jetpack์ ํ์ ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ์์ ํ์ ๊ตฌํ
developer.android.com
#2-1 ๋์๋

```Mermaid
%%{init: {
"flowchart": {
"defaultRenderer": "elk",
"curve": "basis",
"nodeSpacing": 45,
"rankSpacing": 70
},
"themeVariables": {
"clusterBkg": "#eaeaea",
"clusterBorder": "#f0f1f2",
"mainBkg": "#fafafd",
"primaryColor": "#fafafd",
"textColor": "#202020",
"primaryTextColor": "#202020",
"tertiaryTextColor": "#202020",
"background": "#ffffff",
"edgeLabelBackground": "#ffffff"
},
"themeCSS": ".cluster-label text { font-size: 20px !important; font-weight: 700 !important; }"
}}%%
flowchart LR
subgraph appGroup["MainActivity.kt (์ฑ ํ๋ฉด)"]
direction TB
MyApp
HomeDestination["HomeDestination"]
ArticleDestination["ArticleDestination(id)"]
SettingDestination["SettingDestination"]
end
subgraph frameworkGroup["androidx.navigation (Navigation 2 ํ๋ ์์ํฌ)"]
direction TB
NavHost
NavController
end
MyApp -->|"์์ฑ ๋ฐ backStack ์ํ ๊ตฌ๋
"| NavController
MyApp -->|ํธ์ถ| NavHost
NavHost -->|์ฐธ์กฐ| NavController
NavHost -->|๋ชฉ์ ์ง ๋ฑ๋ก| HomeDestination
NavHost -->|๋ชฉ์ ์ง ๋ฑ๋ก| ArticleDestination
NavHost -->|๋ชฉ์ ์ง ๋ฑ๋ก| SettingDestination
HomeDestination -->|ํธ์ถ| NavController
ArticleDestination -->|ํธ์ถ| NavController
SettingDestination -->|ํธ์ถ| NavController
classDef myAppNode stroke:#64748B,stroke-width:3px
class MyApp myAppNode
classDef navHostNode stroke:#3B82F6,stroke-width:3px
class NavHost navHostNode
classDef navControllerNode stroke:#A855F7,stroke-width:3px
class NavController navControllerNode
classDef homeDestNode stroke:#EAB308,stroke-width:3px
class HomeDestination homeDestNode
classDef articleDestNode stroke:#06B6D4,stroke-width:3px
class ArticleDestination articleDestNode
classDef settingDestNode stroke:#F97316,stroke-width:3px
class SettingDestination settingDestNode
linkStyle 0 stroke:#64748B,stroke-width:3.5px
linkStyle 1 stroke:#64748B,stroke-width:3.5px
linkStyle 2 stroke:#3B82F6,stroke-width:3.5px
linkStyle 3 stroke:#3B82F6,stroke-width:3.5px
linkStyle 4 stroke:#3B82F6,stroke-width:3.5px
linkStyle 5 stroke:#3B82F6,stroke-width:3.5px
linkStyle 6 stroke:#EAB308,stroke-width:3.5px
linkStyle 7 stroke:#06B6D4,stroke-width:3.5px
linkStyle 8 stroke:#F97316,stroke-width:3.5px
```
#2-2 MainActivity
package com.example.navigation2basics
...
import androidx.navigation.NavGraph
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
...
import kotlinx.serialization.Serializable
@Serializable
data object Home
@Serializable
data class Article(val id: String)
@Serializable
data object Setting
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Navigation2BasicsTheme {
MyApp()
}
}
}
}
@SuppressLint("RestrictedApi")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyApp() {
val navController = rememberNavController()
val backStack by navController.currentBackStack.collectAsState() // ๋ฐฑ์คํ์ด NavHostController ์์ฒด์ ๋ฐํ์๋ ๋ชจ์ต
val backStackSize = backStack.count {
it.destination !is NavGraph // "!is NavGraph"๊ฐ ์์ผ๋ฉด 'Root NavGraph' ๋ฑ ๋ด๊ฐ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋๋ ๊ฒ๋ค๊น์ง ๋ค size๋ก์ ์กํ๋ฒ๋ฆฐ๋ค
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text("Back stack size: $backStackSize") },
)
},
) { innerPadding ->
NavHost(
navController = navController,
startDestination = Home,
modifier = Modifier.padding(innerPadding),
) {
composable<Home> {
HomeRoute(
navigateToArticle = { articleId ->
navController.navigate(Article(articleId))
},
navigateToSetting = {
navController.navigate(Setting)
}
)
}
composable<Article> { backStackEntry ->
val article = backStackEntry.toRoute<Article>()
ArticleRoute(
id = article.id,
navigateToHome = {
navController.navigate(Home)
},
navigateToSetting = {
navController.navigate(Setting)
},
)
}
composable<Setting> {
SettingRoute(
navigateToHome = {
navController.navigate(Home)
},
navigateToArticle = { articleId ->
navController.navigate(Article(articleId))
},
)
}
}
}
}
@Composable
fun HomeRoute(
navigateToArticle: (String) -> Unit,
navigateToSetting: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Home")
Button(
onClick = {
navigateToArticle("7")
}
) {
Text("Go to 7th Article")
}
Button(
onClick = {
navigateToSetting()
}
) {
Text("Go to Setting")
}
}
}
@Composable
fun ArticleRoute(
id: String,
navigateToHome: () -> Unit,
navigateToSetting: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("${id}th Article")
Button(
onClick = {
navigateToHome()
}
) {
Text("Go to Home")
}
Button(
onClick = {
navigateToSetting()
}
) {
Text("Go to Setting")
}
}
}
@Composable
fun SettingRoute(
navigateToHome: () -> Unit,
navigateToArticle: (String) -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Setting")
Button(
onClick = {
navigateToHome()
}
) {
Text("Go to Home")
}
Button(
onClick = {
navigateToArticle("8")
}
) {
Text("Go to 8th Article")
}
}
}
#2-3 NavHost()
// in NavHost.kt
package androidx.navigation.compose
@Composable public fun NavHost(
navController: NavHostController,
startDestination: Any,
modifier: Modifier = Modifier,
...
builder: NavGraphBuilder.() -> Unit
): Unit
composable์NavGraphBuilder์ ํ์ฅ ํจ์๋ค. ์ฆ,
NavHost(
...
) {
composable<Home> {
HomeRoute(
navigateToArticle = { articleId ->
navController.navigate(Article(articleId))
},
navigateToSetting = {
navController.navigate(Setting)
}
)
}
composable<Article> {
...
}
...
}
์์,
composable()
= this.composable()
= navGraphBuilder.composable()
NavGraphBuilder.composable() ํจ์๊ฐ ์ํ๋๋ ๋งํผ, Navigation์ ๋ชฉ์ ์ง(= Navigation 2์์ Destination์ด๋ผ๊ณ ํํ)๊ฐ ์ถ๊ฐ๋๋ ์ ์ด๋ค.
#2-4 NavController
Jetpack Navigation 2 ๋ผ์ด๋ธ๋ฌ๋ฆฌ(androidx.navigation)์์ ์ ๊ณตํ๋ Navigation ์ํ ๊ด๋ฆฌ ํด๋์ค๋ค. "๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ธฐ๋ณธ ์ ๊ณต"ํ๋ค๋ ์ ์ด ์ค์ํ๋ค. ์ด๊ฒ์ด Navigation 3์ ๋๋น๋๋ ์ง์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค. NavController ๋ด๋ถ์๋ ๋ฐฑ์คํ์ด ์กด์ฌํ๋๋ฐ, (๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ํด) ์๋์ผ๋ก ๊ด๋ฆฌ๋๋ค. ๊ทธ ๋๋ฌธ์ ๋ค๋น๊ฒ์ด์ ๋์์ด ๊ฝค ๋ณต์กํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด ๊ฐ๋ฐ์๊ฐ ๋ง์ง ํ์๊ฐ ์๋ค. ๊ฐ๋ น NavController.navigate() ๋ฑ์ ์ฌ์ฉ์๊ฐ ํธ์ถํ๋ฉด, NavController๊ฐ ์ด ๋ฐฑ์คํ์ ์์์ ํ๋ฉด์ ๋ฃ๊ณ , '๋ค๋ก ๊ฐ๊ธฐ'๊ฐ ํธ์ถ๋๋ฉด ๋ฐ๋๋ก ์์์ pop()์ ์ํํ๋ค.
#2-5 ์ฐธ์กฐ: Now in android์์์ ์ฐ์
Now in Android - Jetpack Navigation 2๊ฐ ์ ์ฉ๋ ๊ตฌ์กฐ ๋ถ์
#1 ๊ฐ์#1-1 ๋ถ์ ๋์ Migrate codebase to Navigation 3 by dturner · Pull Request #1902 · android/nowinandroid[WIP] This PR migrates the codebase to using Jetpack Navigation 3. More details to follow...github.comNow in Android์ 1902๋ฒ PR์ธ, "Mig
kenel.tistory.com
Navigation 2๊ฐ ์ฐ์ธ d438a00 ์ปค๋ฐ ์์ ์ Now in Android๋ฅผ ๋ถ์ํ ๊ฒ์๊ธ์ด๋ค.
#3 Jetpack Navigation 3
ํ์ 3 | App architecture | Android Developers
Navigation 3์ ๋ค๋ก ์คํ, ์ ์ํ ๋ ์ด์์, Android ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ์ ์ฐํ ์ฑ ํ์ ์์คํ ์ ์์ ํ ์ ์ดํ ์ ์๋ ์๋ก์ด Compose ์ค์ฌ ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
developer.android.com
#3-1 ๋์๋

```Mermaid
%%{init: {
"flowchart": {"defaultRenderer": "elk",
"curve": "basis",
"nodeSpacing": 45,
"rankSpacing": 70
},
"themeVariables": {
"clusterBkg": "#eaeaea",
"clusterBorder": "#f0f1f2",
"mainBkg": "#fafafd",
"primaryColor": "#fafafd",
"textColor": "#202020",
"primaryTextColor": "#202020",
"tertiaryTextColor": "#202020",
"background": "#ffffff",
"edgeLabelBackground": "#ffffff"
},
"themeCSS": ".cluster-label text { font-size: 20px !important; font-weight: 700 !important; }"
}}%%
flowchart LR
subgraph appGroup["MainActivity.kt (์ฑ ํ๋ฉด)"]
direction TB
MyApp
NavBackStack["NavBackStack (backStack)
์: [Home, Article(7), Setting]"]
HomeKey["Home : NavKey"]
ArticleKey["Article(id) : NavKey"]
SettingKey["Setting : NavKey"]
end
subgraph frameworkGroup["androidx.navigation3 (Navigation 3 ํ๋ ์์ํฌ)"]
direction TB
EntryProvider
NavEntry["NavEntry
์: [HomeRoute, ArticleRoute, SettingRoute]"]
NavDisplay
end
MyApp -->|"์์ฑ ๋ฐ pop ์ฝ๋ฐฑ(onBack) ์ ์"| NavBackStack
MyApp -->|ํธ์ถ| NavDisplay
NavDisplay -->|์ฐธ์กฐ| NavBackStack
NavDisplay -->|์ฐธ์กฐ| EntryProvider
HomeKey -->|resolve| EntryProvider
ArticleKey -->|resolve| EntryProvider
SettingKey -->|resolve| EntryProvider
EntryProvider -->|"Key → NavEntry ๋ณํ"| NavEntry
NavDisplay -->|์ฐธ์กฐ| NavEntry
NavEntry -->|ํธ์ถ| NavBackStack
classDef myAppNode stroke:#64748B,stroke-width:3px
class MyApp myAppNode
classDef navDisplayNode stroke:#3B82F6,stroke-width:3px
class NavDisplay navDisplayNode
classDef backStackNode stroke:#A855F7,stroke-width:3px
class NavBackStack backStackNode
classDef entryProviderNode stroke:#EAB308,stroke-width:3px
class EntryProvider entryProviderNode
classDef homeKeyNode stroke:#06B6D4,stroke-width:3px
class HomeKey homeKeyNode
classDef articleKeyNode stroke:#F97316,stroke-width:3px
class ArticleKey articleKeyNode
classDef settingKeyNode stroke:#EC4899,stroke-width:3px
class SettingKey settingKeyNode
classDef navEntryNode stroke:#10B981,stroke-width:3px
class NavEntry navEntryNode
linkStyle 0 stroke:#64748B,stroke-width:3.5px
linkStyle 1 stroke:#64748B,stroke-width:3.5px
linkStyle 2 stroke:#3B82F6,stroke-width:3.5px
linkStyle 3 stroke:#3B82F6,stroke-width:3.5px
linkStyle 4 stroke:#06B6D4,stroke-width:3.5px
linkStyle 5 stroke:#F97316,stroke-width:3.5px
linkStyle 6 stroke:#EC4899,stroke-width:3.5px
linkStyle 7 stroke:#EAB308,stroke-width:3.5px
linkStyle 8 stroke:#3B82F6,stroke-width:3.5px
linkStyle 9 stroke:#10B981,stroke-width:3.5px
``` NavEntry๋ ๋ชฉ์ ์ง(Destination)๋ฅผ ์๋ฏธํ๋ฉฐ, ์ค์ ํ๋ฉด("composable content")์ ๋ค์ํ ์ ๋ณด("metadata") ๋ฑ์ ์ง๋๋ ๊ฐ์ฒด๋ค. ๋, ๋ชฉ์ ์ง ๋ณ๋ก Key ๊ฐ๋
์ด ์๊ฒผ๋ค. ๋ชฉ์ ์ง๊ฐ ํต์งธ๋ก ๊ด๋ฆฌ๋๋ ๊ฒ์ด ์๋๋ผ Key(์ด๋ฆํ)๋ฅผ ํตํด ๊ฐ๋ณ๊ฒ ๊ด๋ฆฌ๋๋ ๊ฒ์ด๋ค. ์ด์ ๋ฐ๋ผ EntryProvider๋ผ๋ ํด๋์ค๋ ์๊ฒผ๋๋ฐ, Key๋ฅผ NavEntry๋ก ์ฐ๊ฒฐ(๋ณํ) ์ง๋ ์ญํ ์ ์ํํ๋ค. ํ๋ก๊ทธ๋๋จธ๊ฐ Key๋ง ์ ๊ฒฝ ์จ์ฃผ๋ฉด, EntryProvider์ ์ํด UI๊ฐ ์์์(์ ์ธ์ ์ผ๋ก) ๊ฐฑ์ ๋๋ ์์ด๋ค.
NavEntry๊ฐ NavBackStack์ ์์กดํ๋ค๋ ๊ฒ์ ์ด๋ก ์์ผ๋ก๋ ์ณ์ ์ ์์ง๋ง, ์ฌ์ค ์ค๋ฌด์ ์ผ๋ก๋ ๊ฑฐ์ ํ๋ฆฐ ๋ง์ด๋ค. ์ด๋ค ๋ชฉ์ ์ง A์์ B๋ก ๊ฐ๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋, ์ฆ, A Entry์์ B Entry๋ก ๊ฐ ๋ NavBackStack์ A๊ฐ ์ง์ ์กฐ์ํ๋ ๊ฒฝ์ฐ๋ ์์ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์ค์ ์๋น์ค๋๋ ์ฑ์ด๋ผ๋ฉด, ๋๋ถ๋ถ A๋ "app" ๋ชจ๋๋ก๋ถํฐ ํด๋น ์ด๋ฒคํธ๋ฅผ State Hoisting์ผ๋ก ์ ๋ฌ๋ฐ์ ์ธ ๊ฒ์ด๋ค.
#3-2 MainActivity
package com.example.navigation3basics
...
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
...
import kotlinx.serialization.Serializable
@Serializable
data object Home : NavKey
@Serializable
data class Article(val id: String) : NavKey
@Serializable
data object Setting : NavKey
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Navigation3BasicsTheme {
MyApp()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyApp() {
val backStack = rememberNavBackStack(Home)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text("Back stack size: ${backStack.size}")
},
)
},
) { innerPadding ->
NavDisplay(
backStack = backStack,
modifier = Modifier.padding(innerPadding),
onBack = {
backStack.removeLastOrNull()
},
entryProvider = entryProvider {
entry<Home> {
HomeRoute(
navigateToArticle = { articleId ->
backStack.add(Article(articleId))
},
navigateToSetting = {
backStack.add(Setting)
},
)
}
entry<Article> { article ->
ArticleRoute(
id = article.id,
navigateToHome = {
backStack.add(Home)
},
navigateToSetting = {
backStack.add(Setting)
},
)
}
entry<Setting> {
SettingRoute(
navigateToHome = {
backStack.add(Home)
},
navigateToArticle = { articleId ->
backStack.add(Article(articleId))
},
)
}
},
)
}
}
@Composable
fun HomeRoute(
navigateToArticle: (String) -> Unit,
navigateToSetting: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Home")
Button(
onClick = {
navigateToArticle("7")
}
) {
Text("Go to 7th Article")
}
Button(
onClick = {
navigateToSetting()
}
) {
Text("Go to Setting")
}
}
}
@Composable
fun ArticleRoute(
id: String,
navigateToHome: () -> Unit,
navigateToSetting: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("${id}th Article")
Button(
onClick = {
navigateToHome()
}
) {
Text("Go to Home")
}
Button(
onClick = {
navigateToSetting()
}
) {
Text("Go to Setting")
}
}
}
@Composable
fun SettingRoute(
navigateToHome: () -> Unit,
navigateToArticle: (String) -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Setting")
Button(
onClick = {
navigateToHome()
}
) {
Text("Go to Home")
}
Button(
onClick = {
navigateToArticle("8")
}
) {
Text("Go to 8th Article")
}
}
}
์ฝ๋์์ ๊ฐ์ฅ ๋์ ๋๋ ์ฐจ์ด๋ ๋ฐ๋ก NavController๊ฐ ์ ๊ฑฐ๋์๋ค๋ ์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ NavController๊ฐ ์์์ ๊ด๋ฆฌํด ์ฃผ๋ ๋ฐฑ์คํ์ ์ด์ ํ๋ก๊ทธ๋๋จธ์ธ ์ฐ๋ฆฌ๊ฐ ์ง์ ๊ด๋ฆฌํ๋ค. ๊ทธ๋๋ ํธ์๋ฅผ ์ํ rememberNavBackStack()๋ผ๋ ํฌํผ ํจ์๋ ์ ๊ณตํ๋ค. ์ผ๋ฐ์ ์ผ๋ก๋ ์ ํจ์๋ก ๋ฐฑ์คํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
#3-3 ๋์์ค์ ์ฐจ์ด
๋จ์ด ๋ณ๋ก ๋์์ค์ ์ฐจ์ด๋ฅผ ๊ณฑ์น์ด๋ณด๋ฉด, 2 → 3 ๋ณํ์ ๋ํ ์ดํด๊ฐ ๋ ๊น์ด์ง๋ค.
NavController → ์ ๊ฑฐ๋จ
NavController๊ฐ back stack ์์ → ์ฐ๋ฆฌ(ํ๋ก๊ทธ๋๋จธ)๊ฐ ์์ ใ๊ด๋ฆฌํ๊ฒ ๋ฐ๋
NavController.navigate() → (์ฐ๋ฆฌ๊ฐ ์ง์ ) Key๋ฅผ back stack์ ์ถ๊ฐ
Destination → Entry
Navigation Graph → ์ ๊ฑฐ๋จ
Navigation Graph์ ๋ชฉ์ ์ง(Destination) ํ๋ ๋ฑ๋ก → EntryProvider์์ (Key → Entry) ๊ท์น ํ๋ ์ถ๊ฐ
startDestination → BackStack์ ์ด๊ธฐ Key
NavHost → NavDisplay
Destination → Entry
Destination(๋ชฉ์ ์ง, ๋์ฐฉ์ง)๋ Entry(์ด๋ค ์๋ฃ ๊ตฌ์กฐ ์์ ๋ค์ด ์๋ ํญ๋ชฉ ํ๋)๋ก ๋ฐ๋์๋ค. ์๋ฃ ๊ตฌ์กฐ๋ผ ํจ์ ๋ฐ๋ก ๋ฐฑ์คํ์ ์์ํ๋ค. Destination์ด๋ผ๋ ํํ๋ ์ฌ์ ํ ์ฐ๊ธด ํ๋ค.
Navigation Graph์ ๋ชฉ์ ์ง(Destination) ํ๋ ๋ฑ๋ก → EntryProvider์์ (Key → Entry) ๊ท์น ํ๋ ์ถ๊ฐ
์ฌ์ฉ์๋ NavHost๋ฅผ ํตํด ๋ค๋น๊ฒ์ด์ ๊ทธ๋ํ๋ฅผ ๊ทธ๋ ธ๋ค. Navigation 3์์๋ ๋ค๋น๊ฒ์ด์ ๊ทธ๋ํ๋ฅผ ๊ตฌ์ฑํ๋ Destination๋ค์ EntryProvider๋ก ์ฎ๊ฒจ์ผ ํ๋ค. ํ์ง๋ง, ์ด ๋์ ์ค์ํ ์ฐจ์ด๊ฐ ์๋ค. Entry๋ Entry ๊ฐ ๋ถ๋ชจ-์์ ๊ด๊ณ๋ฅผ ์ด๋ฃจ์ง ์๋๋ค๋ ์ ์ด๋ค. ์ฆ '์๋ฃ ๊ตฌ์กฐ์ '์ผ๋ก๋ ๊ณ์ธต์ด ์๋ ์ํ ๊ด๊ณ๋ค. ํ์ง๋ง ์ฌ์ฉ์์ ์ค๊ณํ๊ธฐ์ ๋ฐ๋ผ ๋ ผ๋ฆฌ์ ๊ณ์ธต์ ์ผ๋ง๋ ์ง ์กด์ฌํ ์ ์๋ค. ๊ฐ๋ น, ๋ฐ๋์ Home์ ๊ฑฐ์ณ Article๋ก ์ ํ๋๋๋ก ํ๋ก๊ทธ๋๋จธ๊ฐ ๊ฐ์ ํ ์ ์๋ค.
NavHost → NavDisplay
Navigation Graph์ ๋ฐ๋ฅธ ํ์ฌ Destination์ UI๋ฅผ ๊ด๋ฆฌ(Hosting)ํ๋ NavHost๋, Navigation Graph๊ฐ ์ฌ๋ผ์ง์ ๋ฐ๋ผ ํ์๊ฐ ์์ด์ก๋ค. NavHost๋ ๋น์ทํ ์ญํ ์ NavDisplay๋ก ๋์ฒด๋์๋๋ฐ ์ด ์ด๋ฆ์, (ํ๋ก๊ทธ๋๋จธ๊ฐ ๊ด๋ฆฌํ๋ ๋ฐฑ์คํ์ ์ข ์๋์ด) ๊ทธ์ ์คํ ์ต์๋จ์ ์๋ ๊ฒ(Entry)์ ํ์(Display)ํ ๋ฟ์ด๋ผ๋ ๋์์ค๋ฅผ ์ง๋๋ค.
#3-4 EntryProvider
// in EntryProvider.kt
package androidx.navigation3.runtime
public inline fun <T : Any> entryProvider(
noinline fallback: (unknownScreen: T) -> NavEntry<T> = {
throw IllegalStateException("Unknown screen $it")
},
builder: EntryProviderScope<T>.() -> Unit,
): (T) -> NavEntry<T> = EntryProviderScope(fallback).apply(builder).build()
↓ ↓ ↓ ์ฝ๊ฒ ํ์ด์ด ๋ฒ์ ↓ ↓ ↓
fun <T : Any> entryProvider(
fallback: (T) -> NavEntry<T>,
builder: EntryProviderScope<T>.() -> Unit,
): (T) -> NavEntry<T> {
// 1. Entry ๋ฑ๋ก ์ ๋ณด๋ฅผ ๋ด์ ๊ฐ์ฒด ์์ฑ (≈ ์์ดํ
์ ๋ด์ List)
val scope = EntryProviderScope<T>(fallback)
// 2. ์ฌ์ฉ์๊ฐ ์์ฑํ DSL ์คํ
scope.builder()
// 3. ๋ฑ๋ก ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก, Key -> NavEntry ๋ณํ ํจ์ ์์ฑ
val provider = scope.build()
// 4. ๊ทธ ํจ์๋ฅผ ๋ฐํ
return provider
}
builder๋ผ๋ ๋งค๊ฐ๋ณ์ ๋ช ์ด๋ ์ ์ฒด์ ์ธ ํ๋ฆ์ ํตํด, builder ํจํด์ ์ฐ๊ณ ์์์ด ๋ณด์ธ๋ค. fallback์ ๋ชจ๋ฅด๋ NavKey๊ฐ ๋ค์ด์์ ๋ ์ด๋ป๊ฒ ํด์ผ ํ๋์ง๋ฅผ ๋ด๋๋ค. ๋ถ๊ธฐ๋ฌธ์์์ 'else'์ ๊ฐ์ ์ญํ ์ ์ํํ๋ ๊ฒ์ด๋ค. "์ฌ์ฉ์๊ฐ ์์ฑํ DSL ์คํ"์ #3-2์ ์๋,
// ์๋ ํจ์๊ฐ builder()๋ผ๋ ์ด๋ฆ์ผ๋ก entryProvider()์ ์ ๋ฌ๋๋ค.
entry<Article> { article ->
ArticleRoute(
id = article.id,
navigateToHome = {
backStack.add(Home)
},
navigateToSetting = {
backStack.add(Setting)
},
)
}
์ ๊ฐ์ ์ฝ๋๋ฅผ ์คํํจ์ ์๋ฏธํ๋ค. ์ค๋ช ์ ์ํด entryProvider()๋ฅผ ํ๋ ์ฝ๋ฉํ์ฌ ๋ณํํด๋ณด๋ฉด,
fun <T : Any> entryProvider(
fallback: (T) -> NavEntry<T>,
builder: EntryProviderScope<T>.() -> Unit,
): (T) -> NavEntry<T> {
// 1. Entry ๋ฑ๋ก ์ ๋ณด๋ฅผ ๋ด์ ๊ฐ์ฒด ์์ฑ (≈ ์์ดํ
์ ๋ด์ List)
val scope = EntryProviderScope<T>(fallback)
// 2. ์ฌ์ฉ์๊ฐ ์์ฑํ DSL ์คํ
scope.entry<Home> {
HomeRoute(
...
)
}
scope.entry<Article> { article ->
ArticleRoute(
...
)
}
scope.entry<Setting> {
...
}
// 3. ๋ฑ๋ก ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก, Key -> NavEntry ๋ณํ ํจ์ ์์ฑ
val provider = scope.build()
// 4. ๊ทธ ํจ์๋ฅผ ๋ฐํ
return provider
}
์ ๊ฐ์ ์ฝ๋๊ฐ ๋๋ค.
#3-5 ๋ฐฑ์คํ ๊ด๋ฆฌ
Entry๊ฐ ๊ณ์ธต(๋ถ๋ชจ-์์) ๊ด๊ณ๋ฅผ ์ด๋ฃจ์ง ์๋๋ค๋ฉด, ๋ฐฑ์คํ ๊ด๋ฆฌ๋ ํ๋ก๊ทธ๋๋จธ์ ๋ชซ์ธ๊ฐ? ๊ทธ๋ ๋ค. ๊ฐ๋จํ ์ฑ์์๋ ๊ด๋ฆฌ๊ฐ ์ด๋ ต์ง ์๊ฒ ์ง๋ง, ์ฑ์ด ์ปค์ง๋ฉด ๋ถ๋ด์ด ์ ์ ๋ ๊ฒ์ด๋ค. Navigation Graph๋ฅผ ๋ง๋ค์ง ์์๋ ๋๋ค๋ ๊ฒ์ ์ฅ์ ์ด์ง๋ง, ์ต์ํ์ ๋ฐฑ์คํ ์กฐ์ ์ฝ๋๊ฐ ์๊ตฌ๋๋ค๋ ๊ฒ์ ๋จ์ ์ผ ์ ์๊ฒ ๋ค. ์ฑ์ด ์ปค์ง๋ ๊ฒฝ์ฐ์, ๋ณ๋์ ๋ฐฑ์คํ ๊ด๋ฆฌ ํด๋์ค๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ค. #3-6์ ์๋ Now in Android์ฒ๋ผ ๋ง์ด๋ค.
#3-6 ์ฐธ์กฐ: Now in android์์์ ์ฐ์
ํ์ฌ ๊ฒ์๊ธ ์์ฑ ์ค
Navigation 3๊ฐ ์ฐ์ธ, 068d8cc ์ปค๋ฐ ์์ ์ Now in Android๋ฅผ ๋ถ์ํ ๊ฒ์๊ธ์ด๋ค. ์ด ํ๋ก์ ํธ์์๋ ๋ฐฑ์คํ์ ๊ด๋ฆฌ๋ฅผ ์ํด NiaNavigator๋ผ๋ ์ ์ฉ ํด๋์ค๋ฅผ ๋ง๋ค์ด ๋ฐฑ์คํ ๊ด๋ฆฌ์ ์ฑ ์์ ๋ชฐ์๋ฃ์๋ค.
#4 ๊ฒฐ๋ก
Jetpack Navigation 2 → 3๋ก,
- ๋ฐฑ์คํ ์์ ๋ฐ ๊ด๋ฆฌ์ ์ฑ ์์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฑ(ํ๋ก๊ทธ๋๋จธ)์ ์ฑ ์์ผ๋ก ๋์ด์๋ค. ๋ฐ๋ผ์ ์ฑ์ ์ฑ ์๊ณผ ์์ ๋๊ฐ ํจ๊ป ์ปค์ก๋ค. ์ฑ์ ๊ท๋ชจ๊ฐ ์๋ค๋ฉด Navigation 3๊ฐ ์คํ๋ ค ๋ถ๋ฆฌํ๋ค. ํ์ง๋ง ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก, (ํ๋ก๊ทธ๋๋จธ ์ ์ฅ์์) ๋ชฉ์ ์ง ์ ํ ๋ก์ง์ ์ง๊ธฐ ์ฝ๋ค๋ ์ด์ ์ด ๊ฐํ๋๋ค.
- Key ๊ฐ๋ ์ด ์๊ฒผ๋ค. Key๋ ๋ฐฑ์คํ์ ๋ค์ด๊ฐ๋ค. ์ด๋ ์ค์ ๋ชฉ์ ์ง๋ ์๋๊ณ , ๋ชฉ์ ์ง์ ๋์ํ๋ ์ด๋ฆํ๋ค. ์ด๋ ๊ฒ ๋ถ๋ฆฌํ ์ด์ ๋ (์๋๋ NavController๊ฐ ์ํํ๋) ๋ชฉ์ ์ง ์ ํ ๋ก์ง๊ณผ ํ๋ฉด์ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๋ก์ง์ ๋ถ๋ฆฌํ๊ธฐ ์ํจ์ด๋ค. ์ ์๋ (๋ฐฑ์คํ์ ์กฐ์ํ๋) ํ๋ก๊ทธ๋๋จธ๊ฐ, ํ์๋ NavDisplay๊ฐ ๋ด๋นํ๋ค. EntryProvider๋ ๊ทธ ์ค๊ฐ์์ Key → Entry ๋ณํ์ ์ํํ๋ค.
- Compose์ ๋ ๋จ์ํ๊ฒ ํตํฉ๋๋ค. ๋ฐฑ์คํ ์์ฒด๊ฐ ์ฑ์ด ์์ ํ๋ State๊ฐ ๋๊ณ , NavDisplay๊ฐ ์ด๋ฅผ ๊ด์ฐฐํ์ฌ UI๋ฅผ ๊ฐฑ์ ํ๋ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
- ๋ณธ ๊ฒ์๊ธ์์ ๋ค๋ฃจ์ง ์์์ง๋ง, ์ฌ๋ฌ destination์ ๋์์ ์ฌ์ฉํ๋ adaptive layout์ด ๊ฐ๋ฅํ๋ค. ๋ฐฑ์คํ ์ต์๋จ์ ์๋ (Key์ ๋์ํ๋) ๋จ์ผ Entry ํ์๋ฅผ ๋์ด, 2๊ฐ ์ด์์ Entry๋ฅผ ํ์ํ๋ ์์ด๋ค.
#5 ์์ค ์ฝ๋
#5-1 Navigation 2
android-practice/navigation/Navigation2Basics at master · Kanmanemone/android-practice
Contribute to Kanmanemone/android-practice development by creating an account on GitHub.
github.com
#5-2 Navigation 3
android-practice/navigation/Navigation3Basics at master · Kanmanemone/android-practice
Contribute to Kanmanemone/android-practice development by creating an account on GitHub.
github.com
'๊นจ์ ๊ฐ๋ ๐ > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Android] Dagger2 - ์ฌ๋ฌ ๊ตฌํ์ฒด ํ๊บผ๋ฒ์ ์ฃผ์ (@IntoSet) (0) | 2026.08.07 |
|---|---|
| [Android] Module ๊ตฌ์กฐ (0) | 2025.09.15 |
| [Android] Pointer input - Drag (0) | 2025.05.13 |
| [Android] App layout - Custom layouts (0) | 2025.02.27 |
| [Android] UI architecture - Phase์ State (1) | 2025.02.27 |