๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ฑ| Android/๐Ÿš€ | Jetpack

[Android, Kotlin] ์ƒˆ๋กœ์›Œ์ง„ Navigation Compose์˜ safe args ์ ์šฉํ•˜๊ธฐ

by immgga 2025. 1. 23.
๋ฐ˜์‘ํ˜•

์ถœ์ฒ˜: unsplash.com

 

 

์„œ๋ก 

์ด๋ฒˆ์— compose ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ํ™”๋ฉด ์ด๋™ ์‹œ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋Š” safe args๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, safe args๊ฐ€ ์ด๋ฒˆ compose ์ตœ์‹  ์—…๋ฐ์ดํŠธ๋กœ ๋” ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณ€๊ฒฝ๋œ ๊ฒƒ ๊ฐ™์•„์„œ ์ด๋ฒˆ ๊ธฐํšŒ์— ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ์ •๋ฆฌํ•ด ๋ณด๋ ค ํ•œ๋‹ค.

 

Navigation 2.8.0์˜ safe args ๋ณ€๊ฒฝ ์‚ฌํ•ญ

์ด๋ฒˆ์— Navigation์˜ ๋ฒ„์ „์ด 2.8.0-alpha08 ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๋˜๋ฉด์„œ kotlin์˜ Serializable์„ ํ†ตํ•ด ์ธ์ž๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๊ฒŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

 

Serializable์„ ํ†ตํ•ด data class๋กœ ํ™”๋ฉด์„ ์ •์˜ํ•˜๊ณ  data class์˜ ํ•„๋“œ๋กœ ๊ฐ’์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ๋ฐ”๋กœ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

 

Serializable, Navigation ์ถ”๊ฐ€

libs.versions.toml์— ์ถ”๊ฐ€ ํ›„ gradle ํŒŒ์ผ์— ์ ์šฉํ•˜์ž(2025.01.23 ๊ธฐ์ค€ ์ตœ์‹  navigation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฒ„์ „: 2.8.5)

kotlin = "2.0.21"
navigationCompose = "2.8.5"

[libraries]
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }

[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

 

 

build.gradle.kts(project)

alias(libs.plugins.kotlin.serialization) apply false

 

 

build.gradle.kts(app)

plugins {
    alias(libs.plugins.kotlin.serialization)
}

dependencies {
    // navigation compose
    implementation(libs.androidx.navigation.compose)
    implementation(libs.kotlinx.serialization.json)
}

 

 

Navigation ๊ตฌํ˜„

์šฐ์„  ํ™”๋ฉด์„ ๋จผ์ € ์ƒ์„ฑํ•œ๋‹ค.

ํ™”๋ฉด๋“ค์€ sealed class๋ฅผ ํ†ตํ•ด ์ •์˜ํ–ˆ๋‹ค.

sealed class Screen {
    @Serializable
    data object LoginScreen: Screen()

    @Serializable
    data object MainScreen: Screen()

    @Serializable
    data class ProfileScreen(val id: Int): Screen()
}

Serializable ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™”๋ฉด๋งˆ๋‹ค ํ•„์ˆ˜์ ์œผ๋กœ ๋‹ฌ์•„์ค˜์•ผ ํ•œ๋‹ค.

 

๋‹ค์Œ์œผ๋กœ ์ƒ์„ฑํ•œ Screen์„ ํ†ตํ•ด NavHost๋ฅผ ๋งŒ๋“ค๊ณ  MainActivity์— ์ ์šฉํ•œ๋‹ค.

@Composable
fun Navigation(modifier: Modifier = Modifier) {
    val navController = rememberNavController()

    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = Screen.LoginScreen
    ) {
        composable<Screen.LoginScreen> {
            LoginScreen(
                ...
            )
        }

        composable<Screen.MainScreen> {
            MainScreen(
                ...
            )
        }

        composable<Screen.ProfileScreen> { backStackEntry ->
            ProfileScreen(
                ...
            )
        }
    }
}

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ๊ธฐ๋ณธ์ ์ธ Navigation ์„ธํŒ… ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๋‹ค๋ฅธ Screen์œผ๋กœ ์ธ์ž ์ „๋‹ฌํ•˜๊ธฐ

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•ด ๋ณด๊ฒ ๋‹ค.

Screen ํŒŒ์ผ์—์„œ๋„ ํ™•์ธํ–ˆ๋‹ค์‹œํ”ผ, ProfileScreen์— id๋ผ๋Š” ํ•„๋“œ๊ฐ€ ์žˆ๋‹ค.

์ € ํ•„๋“œ๋กœ ์‹ค์ œ ProfileScreen์—์„œ id๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

MainScreen์—์„œ ProfileScreen์œผ๋กœ id ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋ ค๊ณ  ํ•  ๋•Œ๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค.

MainScreen์ด ์ „๋‹ฌํ•˜๋Š” ์ชฝ์ด๊ณ , ProfileScreen์ด ์ „๋‹ฌ๋ฐ›์€ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ์ชฝ์ด ๋œ๋‹ค.

 

๋‹ค์‹œ NavHost๋กœ ๋Œ์•„์™€์„œ, ์ „๋‹ฌํ•˜๋Š” ์ชฝ์—์„œ๋Š” ์ด๋™์‹œํ‚ฌ Screen์„ ์ •์˜ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋•Œ ์ด๋™์‹œํ‚ฌ Screen์€ data class ํ˜•ํƒœ์ด์–ด์•ผ ํ•œ๋‹ค.

MainScreen(
    navigateToLogin = {
        navController.popBackStack()
    },
    navigateToProfile = {
        navController.navigate(Screen.ProfileScreen(it))
    }
)

 

navigateToProfile์—์„œ Screen.ProfileScreen์œผ๋กœ navigate ํ•˜๋ฉด์„œ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.

์ธ์ž๋Š” MainScreen์—์„œ ๋ฐ›์•„์„œ ๊ณ ์ฐจ ํ•จ์ˆ˜๋กœ ์ „๋‹ฌํ•ด ์ฃผ๋ฉด ๋œ๋‹ค.

 

์ด์ œ ๋ฐ›๋Š” ์ชฝ์—์„œ๋Š” ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž์‹ ์˜ Screen ์ •๋ณด๋ฅผ ํƒ€์ž…์œผ๋กœ ๊ฐ€์ง€๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

@Composable
fun ProfileScreen(
    navigateToMain: () -> Unit,
    args: Screen.ProfileScreen
) {
    ...
}

 

 

๊ทธ๋ฆฌ๊ณ  NavHost ์ชฝ์—์„œ๋Š” ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ args๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

composable<Screen.ProfileScreen> { backStackEntry ->
    ProfileScreen(
        navigateToMain = {
            navController.popBackStack()
        },
        args = backStackEntry.toRoute<Screen.ProfileScreen>()
    )
}

 

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ตฌํ˜„์ด ๋๋‚œ๋‹ค. ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜์ง€ ์•Š์€๊ฐ€?

 

๊ธฐ์กด์˜ ๋ฐฉ์‹๊ณผ ๋ณ€๊ฒฝ๋œ ์  

๊ธฐ์กด์˜ ๋ฐฉ์‹์€ ๋ฌธ์ž์—ด์„ ๋„˜๊ฒจ์„œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณค์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  route๋กœ ๋ฌธ์ž์—ด์„ ๋„˜๊ธฐ๋Š” ๋Œ€์‹ ์— data class๋ฅผ ์ด์šฉํ•ด serializable ๊ฐ์ฒด๋ฅผ ๋„˜๊ธด๋‹ค.

 

์œ„ 2๊ฐ€์ง€๋งŒ ๋ด๋„ ์ฝ”๋“œ์˜ ์–‘๋„ ์ค„์–ด๋“ค๊ณ  ๊ฐ€๋…์„ฑ์ด ํ–ฅ์ƒ๋œ ๊ฒƒ์ด ์ฒด๊ฐ๋˜์—ˆ๋‹ค.

 

์ƒˆ๋กœ์›Œ์ง„ navigation compose safe args๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ™”๋ฉด๋ผ๋ฆฌ ๊ณต์œ ๋˜๋Š” ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ๊ฐ€ ํ›จ์”ฌ ์ˆ˜์›”ํ•ด์งˆ ๋“ฏํ•˜๋‹ค.

728x90
๋ฐ˜์‘ํ˜•