#1 개요
이전 게시글에서 구축한 Room을 ViewModel에 생성자 주입하여, Room(Model)을 참조할 수 있게 만든다.
#2 코드 - 이벤트 추가
#2-1 이벤트 추가
// package com.example.nutri_capture_new.nutrient
import com.example.nutri_capture_new.db.Meal
import java.time.LocalDate
sealed class NutrientViewModelEvent {
data object InitializeState : NutrientViewModelEvent()
data object LoadMoreItemsAfterLastDate : NutrientViewModelEvent()
data object LoadMoreItemsBeforeFirstDate : NutrientViewModelEvent()
data class InsertMeal(val meal: Meal, val date: LocalDate) : NutrientViewModelEvent()
data class DeleteMeal(val meal: Meal, val date: LocalDate) : NutrientViewModelEvent()
data class GetMealsByDate(val date: LocalDate) : NutrientViewModelEvent()
}
class의 이름은 MainRepository의 함수 이름에서 따왔다. 새로 추가한 이 3개의 클래스가 ViewModelEvent의 자식 클래스인 것에서 알 수 있듯, InsertMealㆍDeleteMealㆍGetMealsByDate는 View에서 트리거되어 ViewModel에서 구현될 이벤트들이다.
#2-2 ViewModel 변경
...
class NutrientViewModel : ViewModel() {
...
// (4) View로부터 받은 이벤트 처리
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
...
is NutrientViewModelEvent.InsertMeal -> {
// TODO
}
is NutrientViewModelEvent.DeleteMeal -> {
// TODO
}
is NutrientViewModelEvent.GetMealsByDate -> {
// TODO
}
}
}
}
ViewModel의 onEvent() 함수 속 When(NutrientViewModelEvent)의 branch들을 추가해준다.
#3 ViewModel 팩토리 추가
#3-1 ViewModel 팩토리 추가
// package com.example.nutri_capture_new.nutrient
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.nutri_capture_new.db.MainRepository
class NutrientViewModelFactory(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(NutrientViewModel::class.java)) {
return NutrientViewModel(repository) as T
}
throw IllegalArgumentException("Unknown View Model Class")
}
}
이전 게시글에서 만든 Repository를 ViewModel이 참조하는 관계이기에, Repository를 ViewModel의 생성자 인수 자리에 넣어줄 예정이다. ViewModel에 생성자가 존재하는 경우 ViewModel의 팩토리를 만들어주어야 하므로 위와 같은 팩토리 클래스를 만든다. (참조: [Android] ViewModel - 뷰 모델에 인자(Argument) 전달 (ViewModelFactory))
#3-2 ViewModel에 생성자 인수 추가
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
...
}
ViewModel에 생성자 인수를 넣어준다.
#3-3 NutrientScreen 생성자 중 ViewModel 인수의 기본값 제거
...
@Composable
fun NutrientScreen(
...
viewModel: NutrientViewModel,
...
) {
...
}
NutrientScreen의 생성자 인수에 있던, viewModel: NutrientViewModel = viewModel<NutrientViewModel>() 구문을 삭제하고 대신 viewModel: NutrientViewModel를 넣어준다. 왜냐하면 ViewModel은 이제 생성자 인수가 존재하기 때문이다. NutrientScreen의 상위 모듈 즉, MainActivity에서 'Repository가 주입된 ViewModel'을 NutrientScreen에 전달하는 식으로 구조를 바꿀 것이다.
#3-4 MainActivity에서 ViewModel 팩토리를 이용해 NutrientScreen 제작
...
class MainActivity : ComponentActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
setContent {
NutricapturenewTheme {
...
Scaffold(
...
) { ... ->
NavHost(
...
) {
composable(route = Destination.NutrientScreen.route) {
val dao = MainDatabase.getInstance(application).mainDAO
val repository = MainRepository(dao)
NutrientScreen(
scope = scope,
snackbarHostState = snackbarHostState,
viewModel = viewModel(factory = NutrientViewModelFactory(repository))
)
}
composable(route = Destination.StatisticsScreen.route) {
...
}
composable(route = Destination.UserInfoScreen.route) {
...
}
}
}
}
}
}
...
}
...
참조: [Android] Jetpack Compose - ViewModel에서 State 사용하기
#4 View에 표시할 State 구조 변경
#4-1 ScreenState 변경
// package com.example.nutri_capture_new.nutrient
import androidx.compose.runtime.snapshots.SnapshotStateList
import com.example.nutri_capture_new.db.Meal
import java.time.LocalDate
data class NutrientScreenState(
val listOfDateAndMeals: SnapshotStateList<DateAndMeals>
)
data class DateAndMeals(
val date: LocalDate,
val meals: SnapshotStateList<Meal>
)
변경된 ERD 및 Entity 구조에 맞게, ScreenState 구조도 변경한다.
#4-2 ViewModel 코드 업데이트
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
// (1) 화면 표시용 State
private val _nutrientScreenState = mutableStateOf(
NutrientScreenState(
listOfDateAndMeals = SnapshotStateList()
)
)
...
// (2) ViewModel용 내부 변수
...
// (3) View에서 받아 처리할 이벤트
...
// (4) View로부터 받은 이벤트 처리
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
is NutrientViewModelEvent.InitializeState -> {
...
repeat(20) {
_nutrientScreenState.value.listOfDateAndMeals.add(
DateAndMeals(
...
)
)
...
}
...
}
is NutrientViewModelEvent.LoadMoreItemsAfterLastDate -> {
val lastDate = _nutrientScreenState.value.listOfDateAndMeals.last().date
_nutrientScreenState.value.listOfDateAndMeals.add(
DateAndMeals(
...
)
)
}
is NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate -> {
val firstDate = _nutrientScreenState.value.listOfDateAndMeals.first().date
_nutrientScreenState.value.listOfDateAndMeals.add(
...,
DateAndMeals(
...
)
)
}
is NutrientViewModelEvent.InsertMeal -> {
// TODO
}
is NutrientViewModelEvent.DeleteMeal -> {
// TODO
}
is NutrientViewModelEvent.GetMealsByDate -> {
// TODO
}
}
}
}
#4-1의 변경사항에 맞춰 ViewModel의 코드도 업데이트한다.
#4-3 View 코드 업데이트
...
@Composable
fun NutrientScreen(
...
) {
...
LazyColumn(
...
) {
val listOfDateAndMeals = viewModel.nutrientScreenState.value.listOfDateAndMeals
items(listOfDateAndMeals) { dateAndMeals ->
Card(
...
) {
Column(
...
) {
Text(
text = DateFormatter.formatDateForNutrientScreen(dateAndMeals.date),
...
)
...
}
}
}
}
}
#4-1의 변경사항에 맞춰 View의 코드도 업데이트한다.
#5 요약
구축한 Model 데이터를 ViewModel에서 참조할 수 있게 만들었다. 또, View에서 Model 데이터 관련한 이벤트를 ViewModel에 요청할 수 있는 구조를 잡았다.
#6 완성된 앱
#6-1 이 게시글 시점의 Commit
#6-2 본 프로젝트의 가장 최신 Commit
'App 개발 일지 > Nutri Capture' 카테고리의 다른 글
Nutri Capture 백엔드 - View에서 INSERT 트리거 (0) | 2024.10.23 |
---|---|
Nutri Capture 백엔드 - Room의 @DAO, @Database 구현 (1) | 2024.10.23 |
Nutri Capture 백엔드 - 새 ERD와 Room의 @Entity 정의 (1) | 2024.10.23 |
Nutri Capture 프론트엔드 - Card (0) | 2024.10.17 |
Nutri Capture 프론트엔드 - 스크롤 로직 View에 일임 (0) | 2024.10.16 |