Notice
Recent Posts
Recent Comments
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

코린이 탈출기

[RxJava] Giphy API를 활용한 gif 추천 애플리케이션 (2) 본문

Android 공부

[RxJava] Giphy API를 활용한 gif 추천 애플리케이션 (2)

명란파스타 2021. 12. 30. 17:12

 

이전 게시글에서는

RxJava를 사용하여 Giphy API로 랜덤으로 받아온 gif를 recyclerView에 띄워주는 방법에 대해 다루어 보았다.

Giphy API 사용법: https://developers.giphy.com/docs/api/endpoint/#trending 

 

이번에는 사용자가 검색한 키워드에 대한 검색 결과를 나타내볼텐데, throttlingdebouncing 두가지 방법으로 구현해보고 차이점에 대해서도 다루어보려고 한다.

 

예를 들어, 사용자가 "hello" 라는 키워드의 gif들을 검색하는 경우를 생각해보자.

검색창의 text가 변경될 때마다 서버로부터 search 결과를 요청한다면,

"h", "he", "hel", "hell", "hello" 각 text에 대한 결과를 모두 요청하게 될 것이다. 사용자는 "hello"에 대한 결과만 받을 수 있으면 되는데 말이다.

이렇게 불필요한 api 호출을 많이 할 뿐만 아니라, 검색결과를 새로 그려주어야 하기 때문에 리소스 낭비도 심하다.

 

Throttling과 Debouncing

이럴 때, throttling과 debouncing의 개념을 적용하면 성능개선을 기대할 수 있다.

Throttle이란, 이벤트를 일정한 주기마다 발생하도록 하는 기술이다. 예를 들어 Throttle 의 설정시간으로 100 ms 를 주게되면 해당 이벤트는 100ms 동안 최대 한번만 발생하게 된다. 

RxJava는 throttleLast, throttleFirst를 제공하는데

위의 그림과 같이 throttleLast를 사용하면 일정한 주기 내 가장 마지막에 방출된 아이템을 반환한다.

반대로 throttleFirst를 사용하면 일정한 주기 내 가장 먼저 방출된 아이템을 반환하게 된다.

 

 

Debounce란, 아무리 많은 이벤트가 발생해도 모두 무시하고 특정 시간 사이에 어떤 이벤트도 발생하지 않았을 때 딱 한번만 마지막 이벤트를 발생시키는 기법이다.

위의 그림에서 이벤트 발생 후 일정 주기 내에 아무런 이벤트가 발생하지 않은 경우에만, 가장 마지막의 이벤트를 방출하는 것을 볼 수 있다.

 

프로젝트 적용

그럼, 실제 프로젝트에서는 이를 어떻게 적용하면 될지 알아보자.

> 앱 실행 시 or 검색창이 비어있는 경우는 random gif들을 20개 띄움

> search mode는 throttling & debouncing 이 있고, radio button으로 선택 가능

> 검색창에 입력 시 선택된 search mode을 적용하여 검색결과를 20개 띄움

val throttlingSubject: PublishSubject<String> = PublishSubject.create()
val debouncingSubject: PublishSubject<String> = PublishSubject.create()


init {
	disposeBag.add(
		throttlingSubject
			.throttleLast(500L, TimeUnit.MILLISECONDS)
			.subscribeOn(Schedulers.io())
			.observeOn(AndroidSchedulers.mainThread())
			.doOnNext {
				Log.d(TAG, "throttling text: $it")
			}
			.subscribe {
				if (it.isBlank()) {
					getRandomGiphy(20)
				} else {
					getSearchGiphyList(it, 20)
				}
			}
	)

	disposeBag.add(
		debouncingSubject
			.debounce(500L, TimeUnit.MILLISECONDS)
			.subscribeOn(Schedulers.io())
			.observeOn(AndroidSchedulers.mainThread())
			.doOnNext {
				Log.d(TAG, "debouncing text: $it")
			}
			.subscribe {
				if (it.isBlank()) {
					getRandomGiphy(20)
				} else {
					getSearchGiphyList(it, 20)
				}
			}
	)
}

 

ViewModel에서 PublishSubject type의 throttlingSubject, debouncingSubject를 생성한다.

 

init 시에

throttlingSubject는 500ms 마다 마지막 이벤트를 다운스트림으로 방출하도록 하고, subscribe 내에서 text가 비어있으면 random gif를 가져오고, 비어있지 않으면 search gif를 가져오도록 한다.

debouncingSubject는 이벤트 발생 이후 500ms동안 이벤트 발생하지 않은 경우에만 다운스트림으로 마지막 이벤트를 내려보내도록 하고, subscribe 내에서는 동일하게 text가 비어있으면 random gif를 가져오고, 비어있지 않으면 search gif를 가져오도록 한다.

 

viewModel.searchQuery.observe(this) {
	when (viewModel.searchMode.value) {
		Constants.SEARCH_MODE_THROTTLING -> {
			viewModel.throttlingSubject.onNext(it)
		}
		Constants.SEARCH_MODE_DEBOUNCING -> {
			viewModel.debouncingSubject.onNext(it)
		}
	}
}

Activity에서는 LiveData로 선언된 searchQuery의 값 변경을 observing하고 있다.

사용자가 검색창에 텍스트를 입력하여 searchQuery가 변경되면 when 블럭이 실행된다.

 

THROTTLING 모드라면, viewModel.throttlingSubject.onNext(it)가 호출되어 현재 텍스트를 발행한다.

DEBOUNCING 모드라면, viewModel.debouncingSubject.onNext(it)가 호출되어 현재 텍스트를 발행한다.

 

결과 영상

현재는 search 결과를 20개만 가져오고 있는데, 다음에는 paging을 통해 스크롤 시 계속해서 보여지도록 리팩토링 해보도록 하겠다.