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

Nutri Capture - ์Šคํฌ๋กค ๋กœ์ง View์— ์ผ์ž„

interfacer_han 2024. 10. 16. 23:07

#1 ๊ฐœ์š”

๊ธฐ์กด์—๋Š” ๋ฌดํ•œ ์Šคํฌ๋กค์„ ์œ„ํ•œ ์•”์‹œ์  ์Šคํฌ๋กค ๋กœ์ง์„ ViewModel๊ณผ View๊ณผ ์–‘๋ถ„ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„œ ์ด ์Šคํฌ๋กค ๊ด€๋ จํ•œ ๋กœ์ง์„ View์— ์ผ์ž„ํ•œ๋‹ค. ViewModel์€ ์ด์ œ LazyColumn์ด ๋ณด์œ ํ•  ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ์˜ ์ถ”๊ฐ€ใ†์ œ๊ฑฐ๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋  ๊ฒƒ์ด๋‹ค.
 

#2 ์ฝ”๋“œ

#2-1 ViewModel์—์„œ์˜ Scroll ๊ด€๋ จ ์ฝ”๋“œ ์‚ญ์ œ

...

class NutrientViewModel : ViewModel() {
    // (1) ํ™”๋ฉด ํ‘œ์‹œ์šฉ State
    ...

    // (2) ViewModel์šฉ ๋‚ด๋ถ€ ๋ณ€์ˆ˜
    ...

    // (3) View์—์„œ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•  ์ด๋ฒคํŠธ
    ...

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

            is NutrientViewModelEvent.LoadMoreItemsAfterLastDate -> {
                ...
            }

            is NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate -> {
                val firstDate = _nutrientScreenState.value.dailyMeals.first().date
                _nutrientScreenState.value.dailyMeals.add(
                    0,
                    DailyMeal(
                        date = firstDate.minusDays(1),
                        meals = SnapshotStateList()
                    )
                )
/* ์ œ๊ฑฐ                
                viewModelScope.launch {
                    _nutrientScreenEventFlow.emit(NutrientScreenEvent.RequestScrollToItem(1))
                }
*/                
            }
        }
    }
}

ViewModel๋ถ€ํ„ฐ ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
 

#2-2 NutrientScreenEvent.RequestScrollToItem ์‚ญ์ œ

...

sealed class NutrientScreenEvent {
    data class ShowSnackbar(val message: String) : NutrientScreenEvent()
/* ์ œ๊ฑฐ
    data class RequestScrollToItem(val index: Int) : NutrientScreenEvent()    
*/
}

์ด์ œ ํ•„์š”์—†๋Š” ์ด๋ฒคํŠธ์ด๋ฏ€๋กœ ์‚ญ์ œํ•œ๋‹ค.
 

#2-3 ์‹คํŒจํ•œ ์‹œ๋„: ์ƒˆ๋กœ์šด LaunchedEffect() ์˜์—ญ ์ƒ์„ฑ

...

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

        // ViewModel๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
        ...
    }

    LaunchedEffect(key1 = viewModel.nutrientScreenState.value.dailyMeals.getOrNull(0)) {
        // ์—ญ๋ฐฉํ–ฅ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„์„ ์œ„ํ•œ ์•”์‹œ์  ์Šคํฌ๋กค
        listState.requestScrollToItem(1, listState.firstVisibleItemScrollOffset)
    }

    LaunchedEffect(key1 = viewModel.isInitialized.value) {
        if(viewModel.isInitialized.value) {
            // ๋ฌดํ•œ ์Šคํฌ๋กค
            ...
        }
    }

    LazyColumn(
        ...
    ) {
        ...
    }
}

[์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ ์ถ”๊ฐ€ → Recomposition]์„ [์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ ์ถ”๊ฐ€ → requestScrollToItem → Recomposition]์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ธ๋ฐ, ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์ƒˆ๋กœ์šด LaunchedEffect() ์˜์—ญ์„ ์„ ์–ธํ•˜๋ฉด ์ด ์ˆœ์„œ๊ฐ€ ์ œ๋Œ€๋กœ ์ง€์ผœ์ง€์ง€ ์•Š๋Š”๋‹ค. LaunchedEffect()๋Š” ์ฝ”๋ฃจํ‹ด ์˜์—ญ์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๊ณ , ๋”ฐ๋ผ์„œ ๋น„๋™๊ธฐ์ ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์œ„ ์ฝ”๋“œ๋Œ€๋กœ ์•ฑ์„ ์‹คํ–‰์‹œ์ผœ๋ณด๋ฉด 0.1์ดˆ์˜ ๋ฒ„๋ฒ…์ž„์ด ๋ˆˆ์— ๋ณด์ธ๋‹ค.
 

#2-4 ์„ฑ๊ณตํ•œ ์‹œ๋„: ๊ธฐ์กด ๋ฌดํ•œ ์Šคํฌ๋กค ๊ด€๋ จ LaunchedEffect() ์ˆ˜์ •

...

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

        // ViewModel๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
        ...
    }

    LaunchedEffect(key1 = viewModel.isInitialized.value) {
        if(viewModel.isInitialized.value) {
            // ๋ฌดํ•œ ์Šคํฌ๋กค
            snapshotFlow { listState.layoutInfo.visibleItemsInfo }.collect { visibleItemsInfo ->
                ...

                if(totalMaxIndex <= firstVisibleItemIndex + visibleItemCount) {
                    ...
                }

                if(firstVisibleItemIndex == 0) {
                    viewModel.onEvent(NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate)
                    // ์—ญ๋ฐฉํ–ฅ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„์„ ์œ„ํ•œ ์•”์‹œ์  ์Šคํฌ๋กค
                    listState.requestScrollToItem(1, listState.firstVisibleItemScrollOffset)
                }
            }
        }
    }

    LazyColumn(
        ...
    ) {
        ...
    }
}

์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ์— ์•„์ดํ…œ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์š”์ฒญ์ธ viewModel.onEvent(NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate) ์ดํ›„ ๋ฐ”๋กœ requestScrollToItem์„ ๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— [์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ ์ถ”๊ฐ€ → requestScrollToItem → Recomposition]๋ผ๋Š” ์ˆœ์„œ๊ฐ€ ๋ฐ˜๋“œ์‹œ ์ง€์ผœ์ง„๋‹ค. ์ฆ‰ 1ํ”„๋ ˆ์ž„์กฐ์ฐจ ๋ฒ„๋ฒ…์ด๋Š” ์ˆœ๊ฐ„์ด ์—†๋‹ค๋Š” ์–˜๊ธฐ๋‹ค.
 

#2-5 ViewModel์— ์žˆ๋˜ 'delay(100)' ์ œ๊ฑฐ

...

class NutrientViewModel : ViewModel() {
    // (1) ํ™”๋ฉด ํ‘œ์‹œ์šฉ State
   ...

    // (2) ViewModel์šฉ ๋‚ด๋ถ€ ๋ณ€์ˆ˜
    ...

    // (3) View์—์„œ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•  ์ด๋ฒคํŠธ
    ...

    // (4) View๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
    fun onEvent(event: NutrientViewModelEvent) {
        when (event) {
            is NutrientViewModelEvent.InitializeState -> {
                ...
                repeat(20) {
                    ...
                }
                
/* ์ œ๊ฑฐ
                viewModelScope.launch {
                    delay(100)
                    _isInitialized.value = true
                }
*/
                _isInitialized.value = true // ์ถ”๊ฐ€
            }

            is NutrientViewModelEvent.LoadMoreItemsAfterLastDate -> {
                ...
            }

            is NutrientViewModelEvent.LoadMoreItemsBeforeFirstDate -> {
                ...
            }
        }
    }
}

์ด์ „ ๊ฒŒ์‹œ๊ธ€์—์„ , ์ดˆ๊ธฐ ํ™”๋ฉด์—์„œ ์ด์ „ ๋‚ ์ด ์ฒซ ์•„์ดํ…œ์œผ๋กœ ๋‚˜์˜ค๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ด ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ViewModel์— ์˜๋ฏธ ์—†๋Š” delay(100) ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋Š”๋ฐ ์ด ๋ถ€๋ถ„์„ ์ œ๊ฑฐํ•œ๋‹ค. ViewModel๊ณผ View์˜ ๋ช…ํ™•ํ•œ ์—ญํ•  ๋ถ„๋ฆฌ๋กœ ์ด์ œ ์Šคํฌ๋กค์ด ๊ผฌ์ผ ์ผ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 

#3 ์š”์•ฝ

ViewModel์ด ์งŠ์–ด์งˆ ํ•„์š”๊ฐ€ ์—†๋Š” ์Šคํฌ๋กค ๊ด€๋ จ ๋กœ์ง์„ View์— ์ผ์ž„ํ–ˆ๋‹ค.
 

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

#4-1 ์ž‘๋™ ์˜์ƒ

์ด์ „ ๊ฒŒ์‹œ๊ธ€๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•œ๋‹ค. ๋ฌผ๋ก , ๋‚ด๋ถ€์ ์œผ๋ก  ๋” ๊ฐ€๋ณ๊ณ  ์ง๊ด€์ ์œผ๋กœ ๋ณ€ํ–ˆ๋‹ค.
 

#4-2 ์ด ๊ฒŒ์‹œ๊ธ€ ์‹œ์ ์˜ Commit

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com

 

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

 

GitHub - Kanmanemone/nutri-capture-new

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

github.com