
기존 Xml로 바텀 네비게이션을 개발 할 때에는 해당 액티비티의 xml에 바텀 네비게이션과 NavHostFragment를 설정하고 xml에 폴더에 navigtaion을 추가하여 개발을 진행 했지만 Jetpack Compose에서는 선언형 UI로 코틀린 코드로 작성하므로 코드 양도 줄고 일관성이 높아지게 되었다.


libs.versions에 해당 라이브러리 버전과 플러그인 라이브러리를 추가를 하고 gradle에 이름을 입력하여 사용 하고 있다.

implementation(libs.navigation.compose) : 기본적인 네비게이션 관련 컴포넌트를 제공
implementation(libs.hilt.navigation.compose) : Hilt와 Jetpack 네비게이션을 통합하는 라이브러리이며 hiltViewModel() 함수를 사용 할 수 있게 하여 네비게이션에 쉽게 뷰모델을 주입 할 수가 있어진다.

@HiltAndroidApp
class App : Application() {}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetPack_BottomNavigationTheme {
MainScreen()
}
}
}
}
@HiltViewModel
class HomeViewModel @Inject constructor() : ViewModel() {}
sealed class BottomNavItem(val route: String, val icon: ImageVector, val title: String) {
data object Home : BottomNavItem("home", Icons.Default.Home, "Home")
data object Search : BottomNavItem("search", Icons.Default.Search, "Search")
data object Profile : BottomNavItem("profile", Icons.Default.Person, "Profile")
}
해당 방식은
sealed class방식으로 각 객체에 대해 다른 프로퍼티나 메서드를 추가할 수 있어 더 유연하지만 단순히 고정된 값 집합이 필요하다면enum class가 더 적합하며 향 후 추가적인 동작이나 속성이 필요한 경우sealed class가 유연하다.
생성자 매개변수
- route : 각 화면의 고유한 경로를 나타내는 문자열
- icon : 각 항목의 아이콘을 나타내는 ImageVector 객체
- title : 각 항목의 제목을 나타내는 문자열
data object 키워드
- Kotlin 1.9부터 도입된 새로운 키워드
- 싱글톤 객체를 생성하면서 동시에 equals(), hashCode(), toString() 메서드를 자동으로 생성
@Composable
fun BottomNavigation(navController: NavHostController) {
val items = listOf(
BottomNavItem.Home,
BottomNavItem.Search,
BottomNavItem.Profile
)
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.title) },
label = { Text(item.title) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
해당 함수에서 네비게이션 바를 구성하여 화면간의 이동을 가능하게 하며 네비게이션의 바의 항목들은
BottomNavItem클래스에서 정의된 화면으로 구성
items : 바텀 네비게이션에 표시될 항목들을 리스트로 정의navBackStackEntry : 백스택 상태를 관찰하여 화면이 변경 될때마다 값이 업데이트 된다.currentRoute : 활성 화된 경로를 가져옴selected = currentRoute == item.route : 현재 경로가 이 항목의 경로와 일치하면 선택된 상태로 표시onClick {...} : 항목이 클릭 시 실행 동작을 정의하며 popUpTo 부분에 시작과 목적지까지 백 스택을 팝업하고 상태를 저장하며 이는 스택이 쌓이는 것을 방지한다.launchSingleTop = true : 동일한 목적지가 이미 스택의 최상위에 있다면 새 인스턴스를 만들지 않음 restoreState = true : 이전의 저장된 상태를 복원 @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
bottomBar = { BottomNavigation(navController) }
) {
NavHost(
navController = navController,
startDestination = BottomNavItem.Home.route
) {
composable(BottomNavItem.Home.route) { HomeScreen() }
composable(BottomNavItem.Search.route) { SearchScreen() }
composable(BottomNavItem.Profile.route) { ProfileScreen() }
}
}
}
Scaffold를 사용하여 기본 레이아웃을 만들고 하단에 네비게이션 바를 배치합니다. NavHost를 통해 각 경로에 해당하는 화면을 정의하여 사용자가 바텀 네비게이션을 통해 다른 화면으로 이동할 수 있게 합니다.
val navController = rememberNavController() :bottomBar = { BottomNavigation(navController) } :NavHost(...) : 네비게이션 그래프를 정의하는 컴포저블로 각 경로에 해당하는 컴포저블을 지정navController = navController : 활성 화된 경로를 가져옴startDestination = BottomNavItem.Home.route :lcomposable(...) : 각 네비게이션 항목에 대한 목적지를 지정@Composable
fun HomeScreen(homeViewModel: HomeViewModel = hiltViewModel()) {
Text("Home Screen")
}
@Composable
fun SearchScreen() {
Text(text = "Search Screen")
}
@Composable
fun ProfileScreen() {
Text(text = "Profile Screen")
}
해당 바텀네비게이션의 각 아이템의 화면이며 현재는 매우 간단한 형태이지만 실제 앱에서는 더욱 확장 될 수 있습니다.
최근 서브 프로젝트에 참여하면서 Jetpack Compose와 Hilt를 사용하여 바텀 네비게이션을 작업 하였는데 기존 XMl에 익숙해서 그런가 Jetpack Compose로 하는 것이 코드가 간결하다는데 익숙하지 않았다.
그래도 네비게이션에 Hilt를 사용하여 의존성 주입을 간편하게 처리할 수 있어 코드의 유지보수성과 확장성을 높일 수 있을 것 같다.
깃허브 : https://github.com/GEUN-TAE-KIM/Jetpack-Compose_BottomNavigation_Sample