App 개발 일지/Nutri Capture

Nutri Capture 백엔드 - View에서 INSERT 트리거

interfacer_han 2024. 10. 23. 15:11

#1 개요

지금까지의 과정을 통해, ViewModel에서 Model을 참조할 수 있게 되었다. 본 게시글에선 ViewModel에서 Model을 참조하는 이벤트를 구현하고, View에서 해당 이벤트를 Trigger하게 만들어본다.
 

#2 코드

#2-1 ViewModel의 이벤트 구현

...

class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
    ...

    // (4) View로부터 받은 이벤트 처리
    fun onEvent(event: NutrientViewModelEvent) {
        when (event) {
            ...

            is NutrientViewModelEvent.InsertMeal -> {
                viewModelScope.launch {
                    var insertedMealId = 0L
                    insertedMealId = repository.insertMeal(event.meal, event.date)
                    if (insertedMealId != 0L) {
                        val dateAndMealsForUpdate =
                            _nutrientScreenState.value.listOfDateAndMeals.find { it.date == event.date }!!
                        dateAndMealsForUpdate.meals.add(event.meal)
                    }
                }
            }

            is NutrientViewModelEvent.DeleteMeal -> {
                viewModelScope.launch {
                    var deletedRowCount = 0
                    deletedRowCount = repository.deleteMeal(event.meal)
                    if (deletedRowCount == 1) {
                        val dateAndMealsForUpdate =
                            _nutrientScreenState.value.listOfDateAndMeals.find { it.date == event.date }!!
                        val mealForDelete =
                            dateAndMealsForUpdate.meals.find { it.mealId == event.meal.mealId }!!
                        dateAndMealsForUpdate.meals.remove(mealForDelete)
                    }
                }
            }

            is NutrientViewModelEvent.GetMealsByDate -> {
                viewModelScope.launch {
                    val mealForUpdate =
                        _nutrientScreenState.value.listOfDateAndMeals.find { it.date == event.date }!!.meals
                    mealForUpdate.clear()
                    mealForUpdate.addAll(repository.getMealsOrderedByTime(event.date))
                }
            }
        }
    }
}

이전 게시글에서 구조만 잡아두고 구현하지 않았던 이벤트 3개를 구현한다. InsertMeal과 DeleteMeal에서, 실제 데이터베이스 쿼리의 반환값을 가지고 쿼리가 성공했는지 실패했는지 판별한 후, 성공한 경우에만 ScreenState에 값을 업데이트하게 만들었다.
 
GetMealsByDate에선 repository.getMealsOrderedByTime(event.date)를 mealForUpdate에 통째로 할당하지 않고 굳이 clear() 및 addAll()을 사용했는데, repository.getMealsOrderedByTime(event.date)를 as 키워드를 통해  SnapshotStateList<Meal>로 타입캐스팅하는 경우에는 에러가 났기 때문이다. 관련해서 자세히 고찰할 법도 하지만 지금은 넘어가고 나중에 리팩토링 시 다듬을만하면 다듬겠다. 어느 정도 앱 개발이 진행되면, 더 이상 기능 추가를 하지 않고 리팩토링만을 집중해서 해볼 생각이다.
 

#2-2 View 변경

...

@Composable
fun NutrientScreen(
    ...
) {
    ...

    LazyColumn(
        ...
    ) {
        ...
        items(listOfDateAndMeals) { dateAndMeals ->
            val date = dateAndMeals.date
            val meals = dateAndMeals.meals

            LaunchedEffect(key1 = true) {
                viewModel.onEvent(NutrientViewModelEvent.GetMealsByDate(date))
            }

            Card(
                ...
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(8.dp)
                ) {
                    Text(
                        text = DateFormatter.formatDateForNutrientScreen(date),
                        ...
                    )

                    Button(
                        onClick = {
                            viewModel.onEvent(
                                NutrientViewModelEvent.InsertMeal(
                                    meal = Meal(
                                        time = LocalTime.now(),
                                        name = "자동 생성된 Meal",
                                        nutritionInfo = NutritionInfo()
                                    ),
                                    date = date
                                )
                            )
                        },
                        modifier = Modifier
                            .align(Alignment.CenterHorizontally)
                            .padding(
                                top = 12.dp,
                                bottom = 12.dp
                            )
                    ) {
                        Text(
                            text = "Meal Insert (현재: ${meals.size}개)",
                            fontSize = 20.sp
                        )
                    }
                }
            }
        }
    }
}

딱 한번 수행되는 LaunchedEffect() 속 viewModel.onEvent(NutrientViewModelEvent.GetMealsByDate(date))에 의해 초깃값이 Recomposition된다. 이후부터는 ViewModel에 정의해두었던 이벤트인 InsertMeal과 DeleteMeal에 의해 Recomposition이 유발된다.
 

#3 요약

View에서 트리거되는 Record INSERT 기능을 구현했다.
 

#4 완성된 앱

#4-1 스크린샷

버튼을 누르면 Meal이 INSERT되고 동시에 Recomposition되어 INSERT된 Record 갯수가 표기된다. 이 변경 사항은 데이터베이스에 기반하기 때문에, 앱을 종료했다가 다시 실행시켜도 동일한 화면이 나온다.
 

#4-2 App Inspection

안드로이드 스튜디오의 App Inspection을 통해 본 데이터베이스 테이블이다. Meal이 잘 INSERT된 모습이다.
 

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

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com

 

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

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com