#1 개요
#1-1 ViewModel 전달하기
ChatBar의 '전송' 버튼을 눌러, NutrientViewModel.onEvent()를 발생시킬 것이다. 현재 MainActivity에서 NutrientViewModel 객체를 만들어서 NutrientScreen에 전달하고 있는데, 같은 방식으로 ChatBar에도 전달한다. 그리고 이 NutrientViewModel에 정의해둔 Insert 동작이, ChatBar의 '전송' 버튼을 누를 때 트리거되게 만든다.
지금은 기존 코드를 복사해서 새로운 기능에 붙여넣기하고 있지만, 후에 Hilt로 마이그레이션해서 이러한 의존성 관련 코드들을 깔끔하게 정리할 예정이다.
#1-2 문제점: UI 업데이트
하지만 #1-1를 프로젝트에 적용해도, #3-1에서 보듯 UI가 업데이트 되지 않는다. '+' 버튼을 누르는 경우에는 잘 되는 것과 대조적으로 말이다.
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
// (1) 화면 표시용 State
private val _nutrientScreenState = mutableStateOf(
NutrientScreenState(
dayMeals = SnapshotStateList()
)
)
val nutrientScreenState: State<NutrientScreenState>
get() = _nutrientScreenState
...
}
...
문제의 원인은 NutrientViewModel이 보유하는 State의 구조에 있다. 현재의 구조는 '+ 버튼 눌림 이벤트'를 감지한 경우에만 _nutrientScreenState에 원소를 하나하나 할당하는 방식이다. 만약, 데이터베이스가 ViewModel의 '+ 버튼 눌림 이벤트' 외 다른 방식으로 변경된다면 _nutrientScreenState는 변하지 않을 것이다.
State를 StateFlow로 변경하고, 그 StateFlow가 데이터베이스를 구독하게 만들어야 한다. 그러면 데이터베이스가 변경될 때마다 StateFlow가 암시적으로 변경될 것이다. 이렇게 바꾸면 현재의 방식보다 훨씬 편하고, 훨씬 깔끔하고, 훨씬 에러의 여지가 적다.
다음 게시글에서 이 변경 사항을 적용한다.
#2 코드
#2-1 MainActivity.kt
...
class MainActivity : ComponentActivity() {
private lateinit var dao: MainDAO
private lateinit var repository: MainRepository
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dao = MainDatabase.getInstance(application).mainDAO
repository = MainRepository(dao)
enableEdgeToEdge()
setContent {
NutricapturenewTheme {
...
Scaffold(
...
bottomBar = {
when (currentRoute) {
Destination.NutrientScreen.route -> NutrientChatBar(
viewModel(
factory = NutrientViewModelFactory(repository)
)
)
else -> MainNavigationBar(navController)
}
},
...
) { ...
NavHost(
...
) {
composable(route = Destination.NutrientScreen.route) {
NutrientScreen(
scope = scope,
snackbarHostState = snackbarHostState,
viewModel = viewModel(factory = NutrientViewModelFactory(repository))
)
}
...
}
}
}
}
}
@Composable
private fun MainNavigationBar(navController: NavHostController) {
...
}
@Composable
fun NutrientChatBar(viewModel: NutrientViewModel) {
Row(
...
) {
...
Box(
...
) {
FilledTonalIconButton(
onClick = {
viewModel.onEvent(
NutrientViewModelEvent.InsertMeal(
meal = Meal(
time = LocalTime.now(),
name = inputtedText.value,
nutritionInfo = NutritionInfo()
),
date = LocalDate.now()
)
)
inputtedText.value = ""
},
...
) {
...
}
}
}
}
}
...
composable(route = Destination.NutrientScreen.route) { ... } 속에 있던, dao 및 repository 초기화 코드를 밖으로 빼서 MainActivity 내에서 사용할 수 있게 한다. 초기화는 onCreate()에서 진행한다.
#2-2 NutrientScreen.kt
...
@Composable
fun NutrientScreen(
...
) {
...
LazyColumn(
...
) {
...
items(...) { ... ->
...
Card(
...
) {
Box(
...
) {
Column(
...
) {
Text(
...
)
Text(
text = "name: ${dayMeal.name} mealId: ${dayMeal.mealId}",
...
)
}
...
}
}
}
}
}
TextField에 입력된 name을 확인할 수 있도록 수정한다.
#3 완성된 앱
#3-1 스크린샷
#1-2 참조
#3-2 이 게시글 시점의 Commit
#3-3 본 프로젝트의 가장 최신 Commit
'App 개발 일지 > Nutri Capture' 카테고리의 다른 글
Nutri Capture 백엔드 - StateFlow로 전환 (0) | 2025.01.02 |
---|---|
Nutri Capture 방향성 - 개발 일정표 (1차) (0) | 2024.12.31 |
Nutri Capture 프론트엔드 - Dimens와 함께 ChatBar 만들기 (0) | 2024.12.31 |
Nutri Capture 프론트엔드 - Typography (2) | 2024.12.28 |
Nutri Capture 프론트엔드 - bottomBar 동적 변경 (1) | 2024.12.18 |