기존 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