이번 앱을 개발하면서 Compose로 Ui를 개발했습니다.
예전에는 XML을 사용하면서 Navigation을 사용했었는데 역시 페이지 이동은 Navigation !
기존에는 개발을 할 때 SAA(Single Activity Architecture)로 개발을 했었는데 그러다 보니 단일 또는 소수의 Activity와 나머지 페이지들은 Fragment로 개발을 했었습니다.
그래서 Navigation으로 개발을 했었는데 Compose로 개발을 하다보니 Fragment를 사용하지 않다보니 화면이동은 어떻게 하지?! 생각을 했었는데 역시 화면이동은 Navigation으로 그대로 하고있었습니다.
기존에는 Navigation은 nav_graph.xml에 아래와 같은 그림 처럼 그래프만 그어주면 됩니다.
참 쉽게 만들어 놓았습니다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/a">
<fragment android:id="@+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="@layout/a">
<action android:id="@+id/action_a_to_b"
app:destination="@id/b"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
</fragment>
<fragment android:id="@+id/b"
android:name="com.example.myapplication.FragmentB"
android:label="b"
tools:layout="@layout/b">
<action android:id="@+id/action_b_to_a"
app:destination="@id/a"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
이런식으로 xml 코드가 작성이 되어있고 action Id만 가져다 쓰면 됩니다.
하지만 Compose는 xml이 없기 때문에 본인이 직접 작성을 해야하는것 같다.. 원래는 fragment 생성해주고 드래그 쓰윽 ~ 하면 알아서 코드 만들어줬었는데 ㅠㅠ
@Composable
fun Navigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.MainScreen.route) {
composable(route = Screen.MainScreen.route) {
MainView(navController = navController)
}
composable(
route = Screen.SearchScreen.route,
arguments = listOf(navArgument(name = "searchId") {
type = NavType.StringType
})
) {
ParallaxEffect(
navigateBack = {
navController.popBackStack()
},
id = it.arguments?.getString("searchId", ""),
)
}
composable(route = Screen.LikeScreen.route) {
LikeCharacterView(navController = navController, navigateBack = {
navController.popBackStack()
})
}
composable(route = Screen.RecentSearchScreen.route) {
RecentSearchView(
navController = navController,
navigateBack = { navController.popBackStack() }
)
}
}
}
해당 코드는 실제 제가 만든 앱 Navigation 코드입니다.
Navgation을 구성하는 요소
NavGraph:
앱의 전체 네비게이션 구조를 정의합니다.
NavGraph는 하나 이상의 Destination으로 구성되며, 각 Destination은 화면이나 일부 화면을 나타냅니다.
NavGraph에 포함된 각 화면이나 대상을 나타냅니다.
Fragment, Activity, 또는 Dialog와 같은 화면을 Destination으로 사용할 수 있습니다.
NavController:
현재의 Destination을 추적하고, 목적지 간의 이동을 관리합니다.
NavController는 UI 구성 요소와 상호 작용하여 목적지 간의 이동을 쉽게 처리할 수 있게 합니다.
NavHost:
NavController를 포함하는 UI 요소로, NavGraph의 일부 목적지를 표시하는 곳입니다.
일반적으로 Fragment 또는 Activity에 내장되어 사용됩니다.
보통 이정도만 알면 기본적인 Navigation을 사용하면 됩니다.
composable(route = Screen.MainScreen.route) {
MainView(navController = navController)
}
하지만 Compose에서는 따로 XML같은 View가 없기 때문에 이게 NavGarph다! 라고 생각하시면 될 것 같습니다.
Compose에서 Navigation을 사용할 때 Screen들을 정의할 필요가 있습니다.
To link the items in a bottom navigation bar to routes in your navigation graph, it is recommended to define a sealed class, such as Screen seen here, that contains the route and String resource ID for the destinations.
Android Compose Navigation Developer에서 나온 말 인데요. Screen을 정의할 때는 Sealed Class를 사용하는 것을 추천한다고 합니다.
그 이유는 stackoverflow에서 본 글을 참조해주세요.
제가 이해한 바로는 인수가 있는 경우는 Sealed Class로 해놓은 경우가 좋은 방법이라고 합니다.
인수가 없다면 사실 안해도 되는 것 같아요. 다른 외국 유튜브를 봤는데 Sealed Class로 만든 사람들은 잘 없더라구요.. 기초적인 Navigation 방법을 만들어줘서 그런 것 같습니다.
저는 인수로 쓸 경우가 있어서 Sealed Class로 만들어 줬습니다.
sealed class Screen(val route: String) {
object MainScreen : Screen("main_screen")
object SearchScreen : Screen("search_screen/{searchId}") {
fun searchCharacter(searchId: String) = "search_screen/$searchId"
}
object LikeScreen : Screen("like_screen")
object RecentSearchScreen : Screen("recentSearch_screen")
}
아무래도 장점은 저런식으로
fun searchCharacter(searchId: String) = "search_screen/$searchId"
이런 식으로 쓸 수 있다는 게 장점인것 같습니다. 추가, 삭제가 편한 것 같아요.
그래서 Screen들을 정의를 해주고
composable(
route = Screen.SearchScreen.route,
arguments = listOf(navArgument(name = "searchId") {
type = NavType.StringType
})
) {
ParallaxEffect(
navigateBack = {
navController.popBackStack()
},
id = it.arguments?.getString("searchId", ""),
)
}
이런식으로 arguments를 넘겨줄 수 있습니다. listOf로 만든 이유는 인자를 여러개를 넘겨줄 때 listOf로 여러개 넣을 수 있습니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaSearchTheme {
Navigation()
}
}
}
}
그리고 마지막으로 MainActivity에서 이렇게 정의를 해주시면 됩니다.
글쓰기가 많이 부족한데 이 글을 읽어주신 분들에게 감사합니다 !
뭔가 기존에 쓰던 Navigation과 같으면서 약간씩 다른 Compose Navigation 기존에 쓰던 분들은 쉽게 배울 것 같습니다. 그리고 기존에 Navigation처럼 이동 할 때 animation을 넣어 줄 수도 있습니다.
참고자료