๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Nutri Capture

Nutri Capture ํ”„๋ก ํŠธ์—”๋“œ - ์—ญ๋ฐฉํ–ฅ ๋ฌดํ•œ ์Šคํฌ๋กค

interfacer_han 2024. 10. 12. 05:30

#1 ๊ฐœ์š”

์ด์ „ ๊ฒŒ์‹œ๊ธ€์—์„œ ์—ญ๋ฐฉํ–ฅ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๋ถˆ์™„์ „ํ•˜๊ฒŒ ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค. ๋ฌธ์ œ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค.

 

 1 

๋ˆˆ์— ๋ณด์ด๋Š” Item์˜ ์ธ๋ฑ์Šค ์ค‘ 0์ด ์กด์žฌํ•˜๋ฉด, ์ƒˆ๋กœ์šด Item์„ Loadํ–ˆ๋‹ค.

 

 2 

์ด๋Ÿฌ๋ฉด ์ƒˆ๋กœ Load๋œ Item์˜ ์ธ๋ฑ์Šค๊ฐ€ ๋‹ค์‹œ 0์ด๋˜๋ฉด์„œ ๋™์‹œ์— ๋ˆˆ์— ๋ณด์ด๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฌดํ•œ ์žฌ๊ท€ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ–ˆ์—ˆ๋‹ค.

 

 3 

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์ƒˆ Item์„ Loadํ•จ๊ณผ ๋™์‹œ์— ์Šคํฌ๋กค์„ ์กฐ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•ด์„œ '์ƒˆ ์•„์ดํ…œ์ด ๋ˆˆ์— ๋ณด์ด๊ธฐ ์ „์—' ์Šคํฌ๋กค์„ ์„ฑ๊ณต์‹œํ‚จ๋‹ค๋ฉด ๋ฌดํ•œ ์žฌ๊ท€ํ˜ธ์ถœ์—์„œ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค.

 

๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„   3 ์˜ ์ƒํƒœ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑธ ๋ชฉํ‘œ๋กœ ์žก๋Š”๋‹ค.

 

#2 ์ฝ”๋“œ

#2-1 ๊นจ์•Œ ๋ณ€๊ฒฝ

...

@Composable
fun NutrientScreen(
    scope: CoroutineScope,
    snackbarHostState: SnackbarHostState,
    viewModel: NutrientViewModel = viewModel<NutrientViewModel>(),
    listState: LazyListState = rememberLazyListState()
) {
    ...
}

ํ”„๋กœํผํ‹ฐ listState์˜ ์œ„์น˜๋ฅผ ์ƒ์„ฑ์ž๋กœ ์˜ฎ๊ฒผ๋‹ค. ์ด ํŽธ์ด ๋” ๊น”๋”ํ•ด๋ณด์˜€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

#2-2 ์ด๋ฒคํŠธ ์„ ์–ธ

// package com.example.nutri_capture_new.nutrient

sealed class NutrientScreenEvent {
    ...
    data class ScrollToItem(val index: Int) : NutrientScreenEvent()
}

ViewModel.onEvent()์— ์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate๊ฐ€ ์ธ์ˆ˜๋กœ์„œ ์ „๋‹ฌ๋˜๋ฉด, LazyColumn์ด ํ‘œ์‹œํ•  ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ์˜ ์•ž ๋ถ€๋ถ„์— ์ƒˆ Item์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. ์ƒˆ Item์„ ๋„ฃ์€ ์งํ›„์— #1์—์„œ ๋งํ•œ ์Šคํฌ๋กค ์ž‘์—…์„ View์— ์š”์ฒญํ•ด์•ผ ํ•œ๋‹ค. ํ•ด๋‹น ์š”์ฒญ์„ ์œ„ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์„ ์–ธํ•œ๋‹ค.

 

#2-3 ์ด๋ฒคํŠธ ๊ตฌํ˜„

...

class NutrientViewModel : ViewModel() {
    ...

    // (4) View๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
    fun onEvent(event: NutrientViewModelEvent) {
        when (event) {
            ...

            is NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate -> {
                Log.i("interfacer_han", "(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: ${_nutrientScreenState.value.dailyMeals.size})")
                val firstDate = _nutrientScreenState.value.dailyMeals.first().date
                _nutrientScreenState.value.dailyMeals.add(
                    0,
                    DailyMeal(
                        date = firstDate.minusDays(1),
                        meals = SnapshotStateList()
                    )
                )
                Log.i("interfacer_han", "(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: ${_nutrientScreenState.value.dailyMeals.size})")

                viewModelScope.launch {
                    Log.i("interfacer_han", "(์ด๋ฒคํŠธ ScrollToItem) ํ˜ธ์ถœ")
                    _nutrientScreenEventFlow.emit(NutrientScreenEvent.ScrollToItem(1))
                }
            }
        }
    }
}

viewModelScope์—์„œ StateFlow.emit()์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๋•์ง€๋•์ง€ ๋ถ™์–ด์žˆ๋Š” Log๋ฌธ์€ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ˆœ์„œ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค. #1์—์„œ '์ƒˆ ์•„์ดํ…œ์ด ๋ˆˆ์— ๋ณด์ด๊ธฐ ์ „์—' ์Šคํฌ๋กค์ด ์™„๋ฃŒ๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ํ˜„ ํ”„๋กœ์ ํŠธ์ฒ˜๋Ÿผ ๋น„๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ๋งŽ์ด ์“ฐ์ธ ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ์šฐ ๋‚ด๊ฐ€ ์˜๋„ํ•œ ์ˆœ์„œ๋Œ€๋กœ ์‹ค์ œ ์•ฑ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ Log์„ ํ†ตํ•ด ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ˆœ์„œ๋ฅผ ๋ช…ํ™•ํžˆ ์•Œ์•„๋‘์–ด์•ผ ํ•œ๋‹ค.

 

#2-4 ์ด๋ฒคํŠธ ๋ฐ›๊ธฐ

...

@Composable
fun NutrientScreen(
    ...
) {
    LaunchedEffect(key1 = true) {
        // State ์ดˆ๊ธฐํ™”
        ...

        // ViewModel๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
        viewModel.nutrientScreenEventFlow.collectLatest { event ->
            when (event) {
                ...

                is NutrientScreenEvent.ScrollToItem -> {
                    Log.i("interfacer_han", "(์ด๋ฒคํŠธ ScrollToItem) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: ${viewModel.nutrientScreenState.value.dailyMeals.size})")
                    listState.scrollToItem(event.index)
                    Log.i("interfacer_han", "(์ด๋ฒคํŠธ ScrollToItem) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: ${viewModel.nutrientScreenState.value.dailyMeals.size})")
                }
            }
        }
    }

    LaunchedEffect(key1 = viewModel.isInitialized.value) {
        ...
    }

    LazyColumn(
        ...
    ) {
        val dailyMeals = viewModel.nutrientScreenState.value.dailyMeals
        Log.i("interfacer_han", "(LazyColumn Recomposition) ์•„์ดํ…œ ๊ฐฏ์ˆ˜: ${dailyMeals.size}")
        items(...) { ... ->
            ...
        }
    }
}

View์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๋Š”๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Log๋ฌธ์„ ๋ถ™์—ฌ์ค€๋‹ค. ๋˜, Recomposition ํƒ€์ด๋ฐ๋„ ์•Œ๊ธฐ ์œ„ํ•ด์„œ LazyColumn ์ชฝ์—๋„ Log๋ฌธ์„ ๋„ฃ์—ˆ๋‹ค.

 

#2-5 ๋กœ๊ทธ ํŒŒ์•…

...

(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ํ˜ธ์ถœ
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 44)
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 45)
(์ด๋ฒคํŠธ ScrollToItem) ํ˜ธ์ถœ
(LazyColumn Recomposition) ์•„์ดํ…œ ๊ฐฏ์ˆ˜: 45
(์ด๋ฒคํŠธ ScrollToItem) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 45)
(์ด๋ฒคํŠธ ScrollToItem) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 45)

(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ํ˜ธ์ถœ
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 45)
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 46)
(์ด๋ฒคํŠธ ScrollToItem) ํ˜ธ์ถœ
(LazyColumn Recomposition) ์•„์ดํ…œ ๊ฐฏ์ˆ˜: 46
(์ด๋ฒคํŠธ ScrollToItem) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 46)
(์ด๋ฒคํŠธ ScrollToItem) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 46)

(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ํ˜ธ์ถœ
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 46)
(์ด๋ฒคํŠธ LoadMoreItemsBeforeFirstDate) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 47)
(์ด๋ฒคํŠธ ScrollToItem) ํ˜ธ์ถœ
(LazyColumn Recomposition) ์•„์ดํ…œ ๊ฐฏ์ˆ˜: 47
(์ด๋ฒคํŠธ ScrollToItem) ์‹œ์ž‘ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 47)
(์ด๋ฒคํŠธ ScrollToItem) ๋ (์•„์ดํ…œ ๊ฐฏ์ˆ˜: 47)

...

์‚ฌ์‹ค ํ•ด๋‹น ๋กœ๊ทธ๋Š” ์„ฑ๊ณต์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์Šคํฌ๋กค ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๊ณผ์ •์˜ ๋ถ€์‚ฐ๋ฌผ์ด๋‹ค. ์ด ๊ฒŒ์‹œ๊ธ€์„ ์“ฐ๋Š” ์‹œ์ ์—์„œ๋Š” ์œ„ ๋กœ๊ทธ๋“ค์ด ์ด๋ฏธ ์„ฑ๊ณตํ•œ ์ฝ”๋“œ์˜ ๋ฆฌ๋ทฐ(?)์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. ์–ด์ฐŒ๋๊ฑด, ์œ„ ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ์ƒˆ Item์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ด๋ฒคํŠธ์ธ LoadMoreItemsBeforeFirstDate ์ดํ›„ LazyColumn์˜ Recomposition๊ณผ ScrollToItem ์ด๋ฒคํŠธ๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. Recomposition์ด ScrollToItem ์™„๋ฃŒ๋ณด๋‹ค ๋” ๋จผ์ € ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋˜์—ˆ์ง€๋งŒ ์‹ค์ œ๋ก  ๋ฐ˜๋Œ€์ผ ๊ฒƒ์ด๋‹ค. ๋ฐ˜๋Œ€๋กœ ์ถœ๋ ฅ๋œ ์ด์œ ๋Š” ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ์‹œ์ ์˜ ๋ฌธ์ œ์ผ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค. ๋ฌด์Šจ ๋ง์ด๋ƒ๋ฉด LazyColumn Recomposition์ด๋ผ๋Š” ๋กœ๊ทธ๊ฐ€ ์ •๋ง LazyColumn์ด Recomposition๋˜๋Š” ์ •๋ฐ€ํ•œ ์‹œ์ ์— ์ถœ๋ ฅ๋˜๋Š” ๊ฒŒ ์•„๋‹ ๊ฑฐ๋ผ๋Š” ์–˜๊ธฐ๋‹ค. ์•„๋ฌดํŠผ, ScrollToItem์€ LazyColumn์— ์ƒ์„ฑ์ž ์ฃผ์ž…๋  LazyListState๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, Recomposition๋˜๋Š” ์‹œ์ ์—์„  ํ™”๋ฉด์— ์ƒˆ Item์ด ๊ทธ๋ ค์ง€๊ธฐ ์ „์— (= ๋ˆˆ์— ๋ณด์ด๊ธฐ ์ „์—) ์Šคํฌ๋กค์ด ์ˆ˜ํ–‰๋œ๋‹ค.

 

#3 ์ƒˆ๋กœ์šด ๋ฌธ์ œ์ 

#3-1 ๋ฌธ์ œ์˜ ์ž‘๋™ ์˜์ƒ

 

#3-2 ๋ฌธ์ œ์˜ ์›์ธ

์ด ๋ฌธ์ œ๋Š” ์‚ฌ์šฉ์ž์˜ ํ„ฐ์น˜์— ์˜ํ•ด ๋ฐœ์ƒํ•œ๋‹ค. ์Šคํฌ๋กค์„ ์œ„๋กœ ์˜ฌ๋ฆฌ๋Š” ๋™์ž‘์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์Šคํฌ๋กค์„ 0.3์ดˆ ์ •๋„ ๋‹จ์œ„๋กœ ๋Š์–ด์„œ ํ•˜๋ฉด  1 ์ƒํƒœ์—์„œ  2 ์ƒํƒœ๋กœ ์ž˜ ์ด๋™ํ•œ๋‹ค. ํ•˜์ง€๋งŒ, ์Šคํฌ๋กค์„ ์œ ์ง€ํ•œ ์ฑ„๋กœ (= ์†์„ ํ„ฐ์น˜ ์Šคํฌ๋ฆฐ์—์„œ ๋–ผ์ง€ ์•Š์€ ์ฑ„๋กœ) ๊ฐ€๋งŒํžˆ ๋ƒ…๋‘๋ฉด  ์Šคํฌ๋กค ๊ณ ์ • ์ƒํƒœ๊ฐ€ ๋˜์–ด #2์—์„œ ๊ณต๋“ค์—ฌ ๋งŒ๋“  ์•”์‹œ์  ์Šคํฌ๋กค ์ด๋ฒคํŠธ๊ฐ€ ๋จนํ†ต์ด ๋˜์–ด ๋ฒ„๋ฆฐ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž์˜ ์Šคํฌ๋กค ์š”์ฒญ์„ ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ์‹ ๋“ฑ์ด ์š”๊ตฌ๋  ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค. ๋‹ค์Œ ๊ฒŒ์‹œ๊ธ€์—์„œ ๋ฐ”๋กœ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์ฐฉ์ˆ˜ํ•˜๊ฒ ๋‹ค. 

 

#4 ์š”์•ฝ

๋ฌดํ•œ ์Šคํฌ๋กค์„ ์ผ์œผํ‚ค๋˜ ๊ธฐ์กด์˜ ๋ฌธ์ œ ์›์ธ์„ ์ œ๊ฑฐํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ƒˆ๋กœ์šด ๋ฌธ์ œ๊ฐ€ ๋‚˜์™”๋‹ค.

 

#5 ์™„์„ฑ๋œ ์•ฑ

#5-1 ์ด ๊ฒŒ์‹œ๊ธ€ ์‹œ์ ์˜ Commit

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com

 

#5-2 ๋ณธ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ์ตœ์‹  Commit

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com