[Compose] collectLatest로 최적화하기

: ) YOUNG·2024년 7월 11일
1

안드로이드

목록 보기
23/30
post-thumbnail
post-custom-banner

CollectLatest를 사용해서 이벤트 처리 최적화를 해보자



달력에서 날짜를 선택할 때 이벤트를 처리해서 snackbar가 보이도록 하려고 했는데, 버튼을 연속으로 눌렀을 때 snackbar가 중첩이 되어서 시간이 딜레이로 계속 나오게 됩니다.

하지만 이럴 경우 메모리 사용도 발생하게 되고, 사실 마지막 값을 제외한다면 이전 선택은 불필요하므로 최신 값 기준으로 이벤트 처리를 할 수 있도록 개선하기로 하였습니다.


해당 작업을 진행하기 전에 Collect와 CollectLatest의 차이점을 알아야 합니다.

간단하게 설명하자면 Collect는 작업이 들어온 순서대로 처리하는 것이고 CollectLatest는 마지막에 들어온 최신 값만 처리하게 되는 방법입니다.



설명


val snackbarFlow = MutableSharedFlow<String>()

ViewModel에서 snackbar를 호출하기 위한 함수를 만들어두고 snackbarFlow.emit()이 호출되면 @Composable에서 실행되게 하였습니다.


    LaunchedEffect(recordViewModel.snackbarFlow) {
        recordViewModel.snackbarFlow.collect { message ->
            snackbarHostState.showSnackbar(message)
        }
    }

여기서 collect가 사용되는 이를 collectLatest로 사용함으로써 이전 snackbar 호출에 대한 이벤트를 모두 취소함으로써 최신 이벤트만 처리할 수 있게 됩니다.


그런데 collect 를 사용해도 MutableSharedFlow()의 옵션을 사용해서 약간의 해결 방법이 있긴 합니다.

replayonBufferOverflow를 사용합니다.

replay는 리플레이 버퍼 크기를 조절합니다. 즉, 여러 개의 이벤트가 들어와도 저장될 수 있는 크기를 정해놓는 겁니다.
onBufferOverflow에서는 DROP_OLDESTDROP_LATEST 옵션 2가지로 버퍼가 가장 찼을 때 오래된 값을 버리거나 가장 최근의 값을 버리거나 선택할 수 있습니다.


    val snackbarFlow = MutableSharedFlow<String>(
        replay = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

여기서는 위처럼 옵션을 저장한다면 collect를 사용하면서 최대한 자원을 아끼고 최신 이벤트를 보유할 수 있게 됩니다.


참고로 replay를 0으로 설정하게 되면 에러가 발생하는데요

DROP_OLDEST 또는 DROP_LATEST로 설정하려면 replay 또는 extraBufferCapacity 값이 양수여야 합니다.
이는 SharedFlow가 어느 정도의 버퍼를 가지고 있어야 오버플로우 전략을 적용할 수 있기 때문입니다.



코드



코드는 아래와 같습니다.

Composable Function

@Composable
private fun RecordCalendar(
    calendarViewModel: RecordCalendarViewModel = hiltViewModel(), recordViewModel: RecordViewModel
) {
    val calendarUiState by calendarViewModel.calendarUiState.collectAsState() // 달력 날짜 전체 데이터
    val coroutineScope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    SnackbarHost(hostState = snackbarHostState)

    LaunchedEffect(recordViewModel.snackbarFlow) {
        recordViewModel.snackbarFlow.collectLatest { message ->
            snackbarHostState.showSnackbar(message)
        }
    }

    Surface(
        // modifier = Modifier.wrapContentHeight().verticalScroll(rememberScrollState()), color = White
        modifier = Modifier.wrapContentHeight(), color = White
    ) {
        CalendarWidget(days = DateUtil.daysOfWeek,
            yearMonth = calendarUiState.yearMonth,
            dates = calendarUiState.dates,
            onPreviousMonthButtonClicked = { prevMonth ->
                calendarViewModel.toPreviousMonth(prevMonth)
            },
            onNextMonthButtonClicked = { nextMonth ->
                calendarViewModel.toNextMonth(nextMonth)
            },
            onDateClickListener = {
                coroutineScope.launch {
                    recordViewModel.showSnackbar("날짜 선택")
                }
            })
    }
} // End of RecordCalendar()


ViewModel


@HiltViewModel
class RecordViewModel @Inject constructor() : ViewModel() {
    // val snackbarHostState = SnackbarHostState()
    val snackbarFlow = MutableSharedFlow<String>()

    fun showSnackbar(text: String) {
        viewModelScope.launch {
            snackbarFlow.emit(text)
        }
    }

} // End of RecordViewModel class


Collect를 적용했을 때

Collect를 사용하면 위 영상처럼 클릭 된 이벤트들이 누적되어 snackbar가 계속 나오게 된다.



CollectLatest를 적용했을 때

CollectLatest를 사용하게 되면 최신 이벤트를 기준으로 처리하여 snackbar가 한 번만 표시되고 더는 나오지 않게 된다.








post-custom-banner

0개의 댓글