성장기록지
Flow debounce와 프로젝트 적용(검색 기능 api 호출 최적화) 본문
debounce란?
debounce는 이벤트를 그룹화하고 일정 시간동안 이벤트가 발생하지 않으면 가장 마지막 이벤트를 전달한다.
'penguin' 이란 단어를 검색한다고 해보자.
p -> pe -> pen -> peng -> pengu -> penguin 과 같이 한글자씩 작성하게 될 것이다.
별 다른 조치를 하지 않는다면 글자를 쓸때마다 이벤트가 전달되겠지만,
Debounce로 일정 시간을 지정해주면 그 시간 동안 이벤트가 발생하지 않을 경우
가장 마지막 이벤트를 전달하게 해준다.
예를 들어 debounce를 0.5초로 설정해주고, penguin이라는 단어에서 한글자 작성하는데 0.3초씩 걸린다면,
penguin 단어를 완성 후 0.5초 이후에 이벤트가 전달이 된다.
그림으로 본다면 다음과 같다.
1 ~ 5번의 이벤트를 수행하던 1 ~ 2의 이벤트, 1 ~ 7 등 몇 번의 이벤트를 수행하던
이를 그룹화하여 debounce에 지정된 시간 동안 이벤트가 발생하지 않을 경우 가장 마지막 이벤트를 전달한다.
Kotlin Flow의 debounce
코틀린 Flow의 debounce에는 다음과 같은 예제 코드가 있다.
예제 코드를 직접 구현하여 실행시켜 보았다.
@OptIn(FlowPreview::class)
fun debounce() = flow {
emit(1)
delay(90)
emit(2)
delay(90)
emit(3)
delay(1010)
emit(4)
delay(1010)
emit(5)
}.debounce(1000)
fun main() = runBlocking{
val format = SimpleDateFormat("HH:mm:ss")
debounce().collect{
println("${format.format(System.currentTimeMillis())} $it")
}
}
debounce를 1000ms를 설정하여, 1초 이상 대기를 한 데이터만 방출을 해주므로
아래와 같이 결과값이 나온다.
debounce의 내부 구조
@FlowPreview
public fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T> {
require(timeoutMillis >= 0L) { "Debounce timeout should not be negative" }
if (timeoutMillis == 0L) return this
return debounceInternal { timeoutMillis }
}
1. require를 통해 timeoutMillis가 양수인지 확인하고, 음수일떄는 throw 해준다.
2. timeoutMillis가 0이면 flow를 그대로 return 해준다.
3. 위의 경우가 아니라면 debounceInternal { timeoutMillis } 를 return 해준다.
람다를 활용하는 debounce
예시코드처럼 람다를 통해 조건문도 적용 해줄 수 있다.
it이 1일 때는 다른 제한 시간을 적용하여
1,3,4,5가 출력되는 모습을 볼 수 있다.
적용 코드는 다음과 같다.
@FlowPreview
@OptIn(kotlin.experimental.ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
public fun <T> Flow<T>.debounce(timeoutMillis: (T) -> Long): Flow<T> =
debounceInternal(timeoutMillis)
프로젝트 적용
현재 프로젝트에는 EditText의 변경될 때 콜백을 통해
StateFlow인 searchText에 값을 넣도록 되어있다.
searchText에 debounce를 활용하여
0.5초동안 이벤트가 발생하지 않으면 api 호출을 진행하도록(getShoppingItems()) 적용시켜주었다.
이를 통해 검색어를 입력하고 있을 때는 api 호출이 일어나지 않도록 최적화를 해주었다.
@HiltViewModel
class SearchViewmodel @Inject constructor(private val searchRepository: SearchRepository) :
ViewModel() {
private val _searchText = MutableStateFlow("")
val searchText = _searchText.asStateFlow()
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val shoppingItems: Flow<PagingData<ShoppingItem>> =
_searchText
.debounce(500L)
.filter { it.isNotEmpty() }
.flatMapLatest { query ->
searchRepository.getShoppingItems(query)
}
.cachedIn(viewModelScope)
'개인 프로젝트' 카테고리의 다른 글
Paging 학습 및 프로젝트 적용 (0) | 2025.01.28 |
---|---|
네트워크 통신 라이브러리 사용 고찰 (HttpUrlConnection, OkHttp, retrofit) (0) | 2025.01.15 |
ShapeableImageView로 이미지 뷰 radius 적용 (w.내부 구현) (0) | 2025.01.12 |
개인 프로젝트) 디자인 키트 구매, 활용 api 정하기 (mockup api, 네이버 쇼핑 api) (0) | 2025.01.05 |
개인 프로젝트) 주제와 기술적 도전 기획 (0) | 2025.01.03 |