Rxを使用せず、Flowを使用して複数回のクリックを防止


🤔 悩みの始まり


既存のRxJavaのコードをFlowで置き換えるときに遭遇するブロックはthrottleFirst()である.複数回クリックしないようにするには、RxJavaのthrottleFirst()演算子が必要です.Flowでは、この機能の演算子はサポートされていません.この演算子は、しばらくの間、最初の値のみを発行し、残りの値は無視されます.この演算子を直接実装することにした.

🆖 既存のRxJavaを使用したマルチタッチコード

private fun View.setRxBindingClicks() {
		this.clicks()
				.throttleFirst(1000, TimeUnit.MILLISECONDS)
				.subscribe { onClick(id) }
				.addTo(disposeBag)
}

private fun onClick(id: Int) {
		when (id) {
				R.id.______ -> clickEvent()
		}
}
  • clicks() : RxBinding. 必要なRx演算子を使用するには、クリックイベントを観察可能な形式に変換します.
  • throttleFirst(1000, TimeUnit.MILLISECONDS):Rx演算子.期間内に発生した最初のイベントを無視し、残りのイベントを無視します.
  • 🆕 Rx → Flow


    演算子のカスタマイズ

    // FlowExtensions.kt
    
    // 클릭 이벤트를 flow로 변환
    fun View.clicks(): Flow<Unit> = callbackFlow {
        setOnClickListener {
            this.trySend(Unit)
        }
        awaitClose { setOnClickListener(null) }
    }
    
    // 마지막 발행 시간과 현재 시간 비교해서 이벤트 발행, 나머지는 무시.
    fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
        var lastEmissionTime = 0L
        collect { upstream ->
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastEmissionTime > windowDuration) {
                lastEmissionTime = currentTime
                emit(upstream)
            }
        }
    }
    ▼▼▼▼▼▼▼4▼▼▼▼▼▼▼▼▼▼▼▼▼4▼▼▼▼debounce-特定のmsに新しいテキストが入力されていない場合はsearch()が呼び出されます.

    活用する

    // ViewExtensions.kt
    
    fun View.setClickEvent(
        uiScope: CoroutineScope,
        windowDuration: Long = THROTTLE_DURATION,
        onClick: () -> Unit,
    ) {
        clicks()
            .throttleFirst(windowDuration)
            .onEach { onClick.invoke() }
            .launchIn(uiScope)
    }
    // ____Activity.kt
    
    button.setClickEvent(lifecycleScope) {
    		Log.i("[TAG]", "click - ${System.currentTimeMillis()}")
    }

    🔗 リファレンス


  • https://github.com/Kotlin/kotlinx.coroutines/issues/1446

  • From RxJava to Kotlin Flow: Throttling