App 개발 일지/Nutri Capture

Nutri Capture 프론트엔드 - NavHost

interfacer_han 2024. 9. 26. 18:59

#1 개요

#1-1 Scaffold의 bottomBar

 

Nutri Capture 프론트엔드 - Scaffold

#1 개요#1-1 첫 삽 뜨기무엇부터 손을 대야할 지 감이 잡히지 않는다. 프로토타이핑을 한번 더 해야 하나? 소프트웨어 모델링이라는 건 뭐지... 이걸 먼저 공부해야할까? ...  여러 의문이 든다. 그

kenel.tistory.com

이전 게시글에서 Scaffold를 만들었다. 해당 Scaffold에는 대부분 앱의 전형적인 구조로서 포함되는 NavigationBar를 넣을 것이다. 그러기 위해서 먼저, NavHost부터 만들어둘 필요가 있다.
 

#1-2 NavHost 구현

 

[Android] Jetpack Compose - Navigation 기초

#1 개요#1-1 전통적인 안드로이드 프로젝트에서의 Navigation [Android] Navigation - 기초#1 이전 글 [Android] Navigation - 환경 설정 #1 Navigation#1-1 액티비티 및 프래그먼트 구성의 트렌드요즘 안드로이드 개발

kenel.tistory.com

위 게시글에 기반하여 #2의 코드를 작성했다.
 

#1-3 Destination 구성

1. NutrientInputScreen: 사용자가 먹은 음식에 대해 기록하는 화면
2. StatisticsScreen: 지금까지 기록한 식단에 대한 통계 화면
3. UserInfoScreen: 사용자 자체에 대한 정보 화면

NavHost 및 NavigationBar가 보유할 Destination을 3가지로 상정했다. 나중에 변경될 가능성도 있지만, 현재 시점에서 구현할 것이라고 예상되는 화면이다.
 

#2 코드

#2-1 모듈 수준 build.gradle.kt 및 lib.versions.toml 수정

plugins {
    ...
}

android {
    ...
}

dependencies {

    ...

    // Navigation
    implementation(libs.androidx.navigation.runtime.ktx)
    implementation(libs.androidx.navigation.compose)
}

Jetpack Compose의 네비게이션 라이브러리를 추가한다.

 

[versions]
...
navigationRuntimeKtx = "2.8.1"

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

[plugins]
...

build.gradle에 추가했던 구문을 안드로이드 컴파일러가 해석할 수 있도록 lib.versions.toml도 수정한다.

 

#2-2 Destination용 @Composable 3개 생성

// in NutrientInputScreen.kt
@Composable
fun NutrientInputScreen(
    scope: CoroutineScope,
    snackbarHostState: SnackbarHostState
) {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        SampleContent(
            text = "NutrientInputScreen",
            modifier = Modifier
                .fillMaxSize()
                .background(Color.LightGray),
            scope = scope,
            snackbarHostState = snackbarHostState
        )
    }
}

// in StatisticsScreen.kt
@Composable
fun StatisticsScreen(
    scope: CoroutineScope,
    snackbarHostState: SnackbarHostState
) {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        SampleContent(
            text = "StatisticsScreen",
            modifier = Modifier
                .fillMaxSize()
                .background(Color.LightGray),
            scope = scope,
            snackbarHostState = snackbarHostState
        )
    }
}

// in UserInfoScreen.kt
@Composable
fun UserInfoScreen(
    scope: CoroutineScope,
    snackbarHostState: SnackbarHostState
) {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        SampleContent(
            text = "UserInfoScreen",
            modifier = Modifier
                .fillMaxSize()
                .background(Color.LightGray),
            scope = scope,
            snackbarHostState = snackbarHostState
        )
    }
}

3개의 컴포저블 함수를 각각 별도의 파일로 만들어두었다. SampleContent()는 MainActivity.kt에 정의해두었던 컴포저블 함수다.
 

#2-3 NavHost 클래스 구현 및 Scaffold의 content 영역에 두기

...

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            NutricapturenewTheme {
                // Navigation 관리의 주체
                val navController = rememberNavController()

                // Snackbar를 위한 CoroutineScope와 State
                val scope = rememberCoroutineScope()
                val snackbarHostState = remember { SnackbarHostState() }

                Scaffold(
                    ...
                ) { innerPadding ->
                    NavHost(
                        navController = navController,
                        startDestination = "nutrientInputScreen",
                        modifier = Modifier.padding(innerPadding)
                    ) {
                        composable(route = "nutrientInputScreen") {
                            NutrientInputScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                        composable(route = "statisticsScreen") {
                            StatisticsScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                        composable(route = "userInfoScreen") {
                            UserInfoScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun SampleContent( ... ) { ... }

content 영역에 있던 SampleContent()를 제거하고, 그 대신 NavHost()를 위치시켰다. NavHost가 innerPadding을 고려하게 두었고, 3개의 Destination에 각각에 CoroutineScope 및 SnackbarHostState를 전달(생성자 주입)한다.
 

#3 요약

Destination용 컴포저블 함수를 3개와 NavHost를 만들었다.
 

#4 완성된 앱

#4-1 스크린샷

가운데에 있는 버튼의 텍스트만 제외하고 이전 게시글과 동일한 스크린샷이 나온다. 내부 구조만 변한 것이다.
 

#4-2 이 게시글 시점의 Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com

 

#4-3 본 프로젝트의 가장 최신 Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com