티스토리 뷰

728x90

1. 프로젝트 구성


스크린샷 2023-03-04 오후 8.00.20.png

이번 프로젝트에서는 처음으로 Multi-Module로 프로젝트를 구성해 보았습니다.

외부 Module를 사용해 본 적은 있지만 직접 만들어서 사용해 보는 것은 이번이 처음입니다.

데이터 통신을 관리하는 network 모듈, 카카오 관련 api를 관리하는 kakao-api 모듈, 데이터베이스를 관리하는 database 모듈 이렇게 3가지로 나누어서 만들어 봤습니다.

Module을 생성 및 등록하는 방법입니다.

1) 모듈 생성

스크린샷 2023-03-04 오후 8.04.36.png

File > New > New Module를 선택해 줍니다.

스크린샷 2023-03-04 오후 8.06.03.png

Templates에서 Android Library를 선택 후 Module name, Package name 등을 설정한 뒤 Finish를 하면 모듈 생성은 끝이 납니다.

스크린샷 2023-03-04 오후 8.00.20.png

모듈이 정상적으로 만들어졌다면 위와 같이 폴더에 차트 모양이 있는 아이콘으로 된 패키지가 만들어질 것입니다.

스크린샷 2023-03-04 오후 8.08.29.png

또한 Gradle Scripts를 보면 새로 만든 Module에 대한 build.gradle이 생긴 것을 확인할 수 있습니다.

여기서 주의할 점은 각각의 build.gradle에서 추가하는 각종 Library들의 버전 중 동일하게 사용하는 것이 있다면 버전을 하나로 통일해 주어야 합니다.

그렇지 않으면 Library 충돌이 발생할 수 있습니다.

ex) :app에서 androidx.core:core-ktx의 버전을 1.9.0을 사용한다고 하면 :database, :kakao-api, :network 역시 1.9.0을 사용합니다.

2) 모듈 등록

스크린샷 2023-03-04 오후 8.25.35.png

Project Structure > Dependencies > app에서 Dependency 위의 + 버튼 누르고 Module Dependency를 선택해 줍니다.

스크린샷 2023-03-04 오후 8.27.24.png

위에서 만든 모듈이 리스트로 나오는데 추가하고 싶은 모듈을 선택 후 ok를 눌러줍니다.

정상적으로 추가가 되었다면 app 수준의 build.gradle에 가면 다음과 같은 코드가 있을 것입니다.

dependencies {

    implementation project(path: ':kakao-api')
    implementation project(path: ':network')
    implementation project(path: ':database')

        ...
}

이렇게 하면 모듈 생성 및 등록이 완료됩니다.

2. 네비게이션


이번에는 제가 기존에 사용하는 방법과는 조금 다르게 작업을 진행하였습니다.

기존에는 각 화면에서 NavController를 가진 RouteAction를 받아서 직접 화면 이동을 호출하였습니다.

이번에는 각 화면에서는 함수 호출을 통해 NavHost를 관리하는 NavgationGraph에게 화면 이동을 요청하는 방식으로 진행하였습니다.

 

 

Navigation 과 Bottom Navigation 구현방법 정리 # Jetpack Compose

오늘은 Jetpack Compose의 Navigation Component와 Bottom Navigation에 대해서 알아보고 구현해 보도록 하겠습니다. 1. 라이브러리 Dependency 설정 Jetpack Compose의 Navigation을 이용하기 위해서는, Compose라이브러리만

developer88.tistory.com

또한 위의 블로그를 참고하여 내비게이션 경로를 만드는 과정도 수정하였습니다.

interface NavItem {
    val title: String
    val route: String
    val routeWithPostFix: String
}

data class MainNavItem(
    override val title: String,
    override val route: String,
    override val routeWithPostFix: String = route
) : NavItem

data class BottomNavItem(
    override val title: String,
    override val route: String,
    override val routeWithPostFix: String = route,
    @DrawableRes
    val icon: Int
) : NavItem

이번 프로젝트에는 내비게이션과 바텀 내비게이션을 같이 사용하게 되어서

공통으로 사용될 부분을 NavItem으로 정의하였습니다.

사실 icon 하나 차이라서 null을 써도 상관은 없지만 null을 최대한 쓰고 싶지 않기도 했고 내비게이션과 바텀 내비게이션에 쓴다는 차이점을 명확하게 하고 싶어서 위와 같이 만들었습니다.

각 할은 다음과 같습니다.

  • title : 내비게이션의 경우 상단 타이틀 제목, 바텀 내비게이션의 경우 아이템의 이름으로 사용
  • route: Navigation에 사용할 경로
  • routeWithPostFix: 상세 경로, 상세 경로가 필요 없을 경우 route와 동일하게 작성
  • icon: 바텀 내비게이션에서 사용할 아이콘
sealed class NavScreen(val item: MainNavItem) {
    object BusStopArrivalInfo: NavScreen(
        MainNavItem(
            title = "",
            route = "BusStopArrivalInfo",
            routeWithPostFix = "BusStopArrivalInfo/{cityCode}/{nodeId}/{name}",
        )
    ) {
        const val CityCode = "cityCode"
        const val NodeId = "nodeId"
        const val Name = "name"
    }

    ...
}

enum class BottomNavItems(val item: BottomNavItem) {
    Home(
        item = BottomNavItem(
            title = "홈",
            route = "Home",
            icon = R.drawable.ic_home
        )
    ),

    ...
}

위의 두 클래스 역시 내비게이션과 바텀 내비게이션을 구분하기 위해 별도로 만들었습니다.

둘 다 enum class로 정의해도 무관하지만 오타 방지를 위해 const를 제공하려고 seald class를 사용하였습니다.

fun makeRouteWithArgs(route: String, vararg args: String): String = buildString {
    append(route)
    args.forEach {
        append("/$it")
    }
}

makeRouteWithArgs()는 위에서 정의한 routeWithPostFix에서 상세 경로를 쉽게 만들기 위해 사용합니다.

@Composable
fun BottomNavigationBar(
    navController: NavHostController,
    onClick: (BottomNavItem) -> Unit
) {
    val items = BottomNavItems.values().map { it.item }
    val backStackEntry by navController.currentBackStackEntryAsState()
    val isVisible = items
        .map{it.routeWithPostFix}
        .contains(backStackEntry?.destination?.route)

    if (isVisible) {
        BottomNavigation(
            backgroundColor = White,
            elevation = 6.dp
        ) {
            items.forEach { item ->
                BottomNavigationItem(
                    selected = item.routeWithPostFix == backStackEntry?.destination?.route,
                    onClick = { onClick(item) },
                    icon = {
                        Icon(painter = painterResource(id = item.icon), contentDescription = item.title)
                    },
                    label = {
                        Text(text = item.title)
                    },
                    selectedContentColor = Green,
                    unselectedContentColor = Gray,
                )
            }
        }
    }
}

바텀 내비게이션을 관리하는 코드입니다.

다른 것들은 대부분 예제에서 사용하는 코드와 동일합니다.

isVisible에서 조금 차이가 있습니다.

저는 하나의 Scaffold에서 모든 내비게이션과 바텀 내비게이션을 같이 사용할 예정인데 그렇게 되면 모든 화면에서 바텀 내비게이션이 출력되는 문제가 발생합니다.

그래서 상세 경로를 비교해서 바텀 내비게이션과 동일한 경로가 아니라면 화면에 출력하지 않도록 작업을 하였습니다.

위에서 내비게이션과 바텀 내비게이션을 별도의 클래스로 만들었기 때문에 바텀 내비게이션의 아이템들만 모아서 비교할 수 있습니다.

@Composable
fun NavigationGraph(
    navController: NavHostController
) {
    val onBackClick: () -> Unit = { navController.popBackStack() }

    NavHost(
        navController = navController,
        startDestination = BottomNavItems.Home.item.routeWithPostFix
    ) {
        /** 홈 화면 **/
        composable(
            route = BottomNavItems.Home.item.routeWithPostFix
        ) {
            HomeScreen()
        }
        /** 교통 화면 **/
        composable(
            route = BottomNavItems.Transportation.item.routeWithPostFix
        ) {
            TransportationScreen(
                goToSearchBusStation = {
                    navController.navigate(NavScreen.BusStationSearch.item.routeWithPostFix)
                },
                goToSearchSubway = {
                    navController.navigate(NavScreen.SubwaySearch.item.routeWithPostFix)
                }
            )
        }
   ...
}

내비게이션을 관리하는 코드입니다.

각 화면에서 페이지 이동이 필요할 경우

goToSearchBusStation = {
    navController.navigate(NavScreen.BusStationSearch.item.routeWithPostFix)
},

위와 같이 함수 파라미터를 이용하여 페이지 이동할 수 있도록 하였습니다.

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    val systemUiController = rememberSystemUiController()
    systemUiController.setSystemBarsColor(Black)

    Scaffold(
        backgroundColor = White,
        bottomBar = {
            BottomNavigationBar(
                navController = navController,
                onClick = {
                    navController.navigate(it.routeWithPostFix)
                }
            )
        }
    ) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(it)
        ) {
            NavigationGraph(
                navController = navController
            )
        }
    }

마지막으로 메인 화면에서 내비게이션과 바텀 내비게이션 설정입니다.

728x90
댓글