-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#3 - 기본 화면 전환 구현 #4
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
진혁님 고생하셨어용
일단 chaeum.xxx
으로 패키지명 하시고,
다음 PR에서 제가 이름 Piece로 바꿔놓겠습니다!!
크게 변경될 점은 없고 약간 수정되어야 할 점이랑 짧은 코멘트 몇 개 남겼어요 ! 체크 부탁드려요.
++
지라 티켓 및 이슈는 PR이 머지되고 닫아주세요!!!!
app/build.gradle.kts
Outdated
alias(libs.plugins.androidx.navigation.safeargs) | ||
alias(libs.plugins.kotlin.serialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 safeargs
는 생각해보니 이전 프로젝트에서 xml + compose로 작업하다보니 네비게이션이 xml기반이어서 넣었던 것이었어요...!
safeargs
빼고 kotlinserilization
만 쓰셔도 될 것 같아요!
죄송합니다 ㅎㅎ,,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아니에요! 요 며칠 circuit 좀 찾아봤는데, circuit은 자체적으로 navigation을 지원해준다고 하더라구요. 그래서 이 부분에 대해서 한번 이야기 해보면 좋을 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 맞아요,,, Circuit을 사용하면 자체적인 ViewModel과 Navigation을 사용하더라고요 ,,,
실제로 circuit을 택할건지에 대한 논의를 추가적으로 해보면 좋을 것 같아요.
@Composable | ||
fun HomeNavHost( | ||
navController: NavHostController, | ||
modifier: Modifier = Modifier, | ||
) { | ||
NavHost( | ||
navController = navController, | ||
startDestination = MatchingGraph, | ||
modifier = modifier, | ||
) { | ||
matchingNavGraph() | ||
myPageScreen() | ||
etcScreen() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 AppNavHost랑 HomeNavHost차이점이 무엇인지 알 수 있을까요 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 AppNavHost는 Auth랑 Home 화면 전환을 담당하고, Home은 Matching, Mypage, Etc 화면 전환을 담당하게 만들었어요.
처음엔 Auth, Matching, Mypage, Etc 4개로 구성했는데, bottom navigation이 없는 Auth를 포함해서 4개를 하나의 NavHost에서 bottom navigation으로 구현하니까 화면이 전환되기 전에 항상 화면이 Auth인지 확인하는 로직을 추가해야 하더라구요. 그래서 몇몇 샘플 코드들을 보고 Host를 분리하는게 더 좋을 것 같아서 분리하는 방식으로 만들었어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러네용 이 때 까지 저는 단일 NavHost로
private fun handleBottomBarState(
currentRoute: String?,
setBottomBarState: (Boolean) -> Unit,
): Unit = when (currentRoute) {
null -> setBottomBarState(false)
signInNavigationRoute -> setBottomBarState(false)
signUpNavigationRoute -> setBottomBarState(false)
splashNavigationRoute -> setBottomBarState(false)
profileSettingNavigationRoute -> setBottomBarState(false)
attendanceManagementNavigationRoute -> setBottomBarState(false)
ManagementSurveyRoute.surveyFormRegistrationRoute -> setBottomBarState(false)
ManagementSurveyRoute.surveyFormEditRoute("{id}") -> setBottomBarState(false)
eventRegistrationNavigationRoute -> setBottomBarState(false)
eventEditNavigationRoute -> setBottomBarState(false)
SurveyRoute.answerRoute("{id}") -> setBottomBarState(false)
surveyCheckRoute -> setBottomBarState(false)
SurveyCheckRoute.surveyDetailRoute("{id}", "{backStack}") -> setBottomBarState(false)
else -> setBottomBarState(true)
}
와 같은 방식으로 Navigation의 가시성을 조절했었는데 스택 오버플로우 찾아보니 2개로도 구현할 수 있겠군요..
당장은 뭐가 더 좋은 방식인지는 잘 모르겠습니다 ㅎㅎ
단일 네비게이션으로 두고 전환할 때 마다 체크하는 대신 가독성 챙기기 vs
유지보수 측면에서 화면이 늘어날 수록 코드를 수정 혹은 작성해야하는 부분이 적은 대신 2개의 NavHost 사용하기
정도가 되겠네요. 이렇게 가도 좋을 것 같아요..!
@Composable | ||
fun rememberAppState( | ||
navController: NavHostController = rememberNavController(), | ||
): AppState { | ||
return remember(navController) { | ||
AppState(navController = navController) | ||
} | ||
} | ||
|
||
class AppState( | ||
val navController: NavHostController, | ||
) { | ||
fun loginSuccess() { | ||
navController.navigate(HomeGraph) { | ||
popUpTo(navController.graph.findStartDestination().id) { | ||
inclusive = true | ||
} | ||
launchSingleTop = true | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AppState를 한 번도 이런식으로 써본 적이 없어서 요 녀석의 역할이 궁금해용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nowinAndroid에서 참고해서 만든 건데 제가 분석한 AppState는 AppNavHost에서 관리하는 화면들에 전역적으로 쓰일 수 있는 상태값들을 저장해서 쓰는 것 같아요!
저희 회사에서는 주로 인터넷 연결 상태 확인, 로그인 상태 확인(중복 로그인시 강제 로그아웃을 위해), 언어 설정 확인, 화면 전환 로직 등을 AppState에서 관리해서 쓰고 있어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한번도 써보지 않았지만 써보면 재밌을 것 같아요....!
feature/auth/build.gradle.kts
Outdated
alias(libs.plugins.androidx.navigation.safeargs) | ||
alias(libs.plugins.kotlin.serialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
safeargs는 지우셔도 될 것 같고,
아마 모든 feature모듈에 kotlin.serilization 플러그인이 들어가고
json 종속성이 임포트 될 것 같은데,
이 부분 chaem.andorid.feature 플러그인에서 처리해주면 보일러플레이트 코드가 더 줄어들 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 좋아요~ 그럼 제가 feature 모듈에 추가해둘까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 좋아요~ 그럼 제가 feature 모듈에 추가해둘까요??
넵 좋습니다~
feature/auth/build.gradle.kts
Outdated
} | ||
|
||
android { | ||
namespace = "com.example.auth" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
패키지 명이 다른 것 같아요!
여기 gradle 말고 나머지 부분도 마찬가지로요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵넵 수정해둘게요!
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | ||
Text( | ||
text = "AuthRoute", | ||
fontSize = 30.sp, | ||
modifier = Modifier.clickable { onLoginSuccess() }) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 UI적인 요소는 Route보다 Screen에 들어가는 것이 맞다고 생각되는데,
그냥 네비게이션 연결용으로 임시로 Route에 ui요소를 둔 것일까요 ?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵넵 맞아요. 그냥 화면을 표시할려고 하다보니 그렇게 작성했었는데, 실제 개발하실 때는 저 부분 지우고 screen에 정의해서 쓰시면 될 것 같아요!!
@Serializable | ||
data object AuthGraph | ||
|
||
sealed class AuthGraphDest { | ||
@Serializable | ||
data object AuthRoute : AuthGraphDest() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Serializable
data object AuthGraph
이 외부로 공개되는 그래프이고,
sealed class AuthGraphDest {
@Serializable
data object AuthRoute : AuthGraphDest()
}
이 AuthGraph내부에서 사용되는 목적지 인가요 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵넵! AuthGraphDest 는 Auth 쪽 화면 전환할 때 쓰기 위해서 만들어 두었어요!
local.properties
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 파일을 Gitignore에 추가해야하겠군요 ,,
enum class TopLevelDestination( | ||
val selectedIcon: ImageVector, | ||
val unselectedIcon: ImageVector, | ||
val iconText: String, | ||
val titleText: String, | ||
val route: KClass<*>, | ||
) { | ||
MATCHING( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "매칭", | ||
titleText = "매칭", | ||
route = MatchingGraphDest.MatchingRoute::class, | ||
), | ||
MYPAGE( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "마이페이지", | ||
titleText = "마이페이지", | ||
route = MyPageRoute::class, | ||
), | ||
ETC( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "ETC", | ||
titleText = "ETC", | ||
route = EtcRoute::class, | ||
), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum class로도 잘 동작하지만,
새롭게 업데이트 된 Docs를 따라서 만들어보는 것은 어떨까요?
data class로 정의하고 List로 묶어둔 것 같은데,
진혁님 생각이 궁금해요...!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 이 부분 NowInAndroid 코드를 참고해서 작성한거긴 한데 (https://github.com/android/nowinandroid/blob/main/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt)
class HomeState(
val navController: NavHostController,
) {
fun navigateToTopLevelDestination(topLevelDestination: TopLevelDestination) {
val topLevelNavOptions = navOptions {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
when (topLevelDestination) {
TopLevelDestination.MATCHING -> navController.navigateToMatching(topLevelNavOptions)
TopLevelDestination.MYPAGE -> navController.navigateToMyPage(topLevelNavOptions)
TopLevelDestination.ETC -> navController.navigateToEtc(topLevelNavOptions)
}
}
}
제가 생각한 nowinandorid에서 enum class를 사용한 이유는 화면 전환 navigate 메서드를 정의할 때 enum의 exhaustive 특성을 활용해서 타입 안전성과 가독성을 높이기 위해 쓴 것 같습니다.
list도 동적으로 화면을 추가하거나 뺄 수 있어서 유연성이 좋다고 생각하지만, navigate 메서드를 정의할 때 컴파일 시점에서 확실히 정의되지 않기 때문에 로직을 작성할 때 불편함이 있을 거라고 생각합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 항상 Enum으로 작성해왔는데 docs에 새롭게 업데이트 되어서 건의해본거였답니다 ㅎㅎㅎㅎㅎ
Enum이 가독성이 더 좋은 것 같긴해요 ..!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가 코멘트 남겼습니다!
작업하신 모든 모듈의 패키지 명만 수정해주세용~~~
app/build.gradle.kts
Outdated
alias(libs.plugins.androidx.navigation.safeargs) | ||
alias(libs.plugins.kotlin.serialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 맞아요,,, Circuit을 사용하면 자체적인 ViewModel과 Navigation을 사용하더라고요 ,,,
실제로 circuit을 택할건지에 대한 논의를 추가적으로 해보면 좋을 것 같아요.
@Composable | ||
fun HomeNavHost( | ||
navController: NavHostController, | ||
modifier: Modifier = Modifier, | ||
) { | ||
NavHost( | ||
navController = navController, | ||
startDestination = MatchingGraph, | ||
modifier = modifier, | ||
) { | ||
matchingNavGraph() | ||
myPageScreen() | ||
etcScreen() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러네용 이 때 까지 저는 단일 NavHost로
private fun handleBottomBarState(
currentRoute: String?,
setBottomBarState: (Boolean) -> Unit,
): Unit = when (currentRoute) {
null -> setBottomBarState(false)
signInNavigationRoute -> setBottomBarState(false)
signUpNavigationRoute -> setBottomBarState(false)
splashNavigationRoute -> setBottomBarState(false)
profileSettingNavigationRoute -> setBottomBarState(false)
attendanceManagementNavigationRoute -> setBottomBarState(false)
ManagementSurveyRoute.surveyFormRegistrationRoute -> setBottomBarState(false)
ManagementSurveyRoute.surveyFormEditRoute("{id}") -> setBottomBarState(false)
eventRegistrationNavigationRoute -> setBottomBarState(false)
eventEditNavigationRoute -> setBottomBarState(false)
SurveyRoute.answerRoute("{id}") -> setBottomBarState(false)
surveyCheckRoute -> setBottomBarState(false)
SurveyCheckRoute.surveyDetailRoute("{id}", "{backStack}") -> setBottomBarState(false)
else -> setBottomBarState(true)
}
와 같은 방식으로 Navigation의 가시성을 조절했었는데 스택 오버플로우 찾아보니 2개로도 구현할 수 있겠군요..
당장은 뭐가 더 좋은 방식인지는 잘 모르겠습니다 ㅎㅎ
단일 네비게이션으로 두고 전환할 때 마다 체크하는 대신 가독성 챙기기 vs
유지보수 측면에서 화면이 늘어날 수록 코드를 수정 혹은 작성해야하는 부분이 적은 대신 2개의 NavHost 사용하기
정도가 되겠네요. 이렇게 가도 좋을 것 같아요..!
enum class TopLevelDestination( | ||
val selectedIcon: ImageVector, | ||
val unselectedIcon: ImageVector, | ||
val iconText: String, | ||
val titleText: String, | ||
val route: KClass<*>, | ||
) { | ||
MATCHING( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "매칭", | ||
titleText = "매칭", | ||
route = MatchingGraphDest.MatchingRoute::class, | ||
), | ||
MYPAGE( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "마이페이지", | ||
titleText = "마이페이지", | ||
route = MyPageRoute::class, | ||
), | ||
ETC( | ||
selectedIcon = Icons.Filled.Call, | ||
unselectedIcon = Icons.Outlined.Call, | ||
iconText = "ETC", | ||
titleText = "ETC", | ||
route = EtcRoute::class, | ||
), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 항상 Enum으로 작성해왔는데 docs에 새롭게 업데이트 되어서 건의해본거였답니다 ㅎㅎㅎㅎㅎ
Enum이 가독성이 더 좋은 것 같긴해요 ..!
1. ⭐️ 변경된 내용
2. 🖼️ 스크린샷(선택)
3. 💡 알게된 부분
Navigation에 Type Safety가 추가되었다는 걸 알게 되었어요. Kotlin Serialization으로 화면 전환간 전달되는 인수에 대한 Type Safety를 보장해준다고 하네요.
참고 자료
https://developer.android.com/develop/ui/compose/navigation?hl=ko
nowinandroid (많이 참고함)
https://velog.io/@chuu1019/Android-Jetpack-Compose-Bottom-Navigation-%EB%A7%8C%EB%93%A4%EA%B8%B0
4. 📌 이 부분은 꼭 봐주세요!
' implementation(libs.kotlinx.serialization.json) '를 추가했는데, plugin에 추가할까 고민하다가 feature에 넣는게 좋을지 library에 kotlinAndroid에 넣는게 좋을지 못 정해서 일단 모든 모듈에 추가해뒀어요. 태규님이 보시고 적절하다 싶으신 곳에 넣어주시면 좋을 것 같아요 (안 넣으셔도 되고)