throttleFirst置換Coルーチンでのクリックまたはダブルクリックを避ける


既存のRxではダブルクリック/複数回クリックを防止するためにthrottleFirstを使用している.
Coルーチンでダブルクリックを防止するにはどうすればいいですか?

Actorの使用


コアコンセプト

private fun View.onClickOnce(action: suspend (View) -> Unit) {
	val event = GlobalScope.actor<View>(Dispathers.Main) {
    	for (event in channel) 
        	action(event)
    }
    
    setOnClickListner {
    	event.offer(it)
    }
}

var currentIndex = 0
fab.onClickOnce {
	10.countDown(currentIndex++)
}

実際の使用方法


1.BaseOneClickActivityの作成

CoroutineScopeおよびAppCompatActivityBaseOneClickActivityを実装/継承する.また,今後ダブルクリックを防止する必要があるactivityはBaseOneClickActivityの使用を継承する.
abstract class BaseOneClickActivity: AppCompatActivity(). CoroutineScope {
	private val job: Job = Job()
    
    override val coroutineContext: CoroutineContext
    	get() = Dispatchers.Main + job
        
    override fun onDestroy() {
    	super.onDestroy()
        job.cancel()
    }
}

abstract class MainActivity : BaseOneClickActivity() {
	launch{} // UI 스레드에서 처리
    launch(Dispatchers.Default) {} // Default 스레드에서 처리
    
    actor<Generic Type> {} // UI 스레드에서 처리
    actor<Generic Type>(Dispatchers.Default) {} // Default 스레드에서 처리
}

2.onClickを作成します。


この内容はちょっと難しいですが、よく見ると…
まずfun <E> View.onClickを見て、ここではCoroutinesSendChannelOnClickEventがクラスを作成して戻ります.このクラスにはconsumeEach()関数があり、コードの最下部にはinfix関数があり、.consume()関数があります.この関数の内部にはconsumeEach()が使用されます.
最終的には、次の用法コードのようにコードをきれいに書くために追加された関数です!
を選択します.onClick(Dispatcher){
このDispatchで実行するアクション
}consumer{//infix関数なので、このモードを使用できます.
プライマリ・スレッドで実行されるアクション
}
class CoroutinesSendChannelOnClickEvent<E>(
    private val view: View,							// View: click을 위한 View
    private val bgBody: suspend (item: View) -> E,	// background에서 실행할 내용
    private val dispatcherProvider: DispatchersProviderSealed = DispatchersProvider,	
    private val job: Job? = null) {					// Job : cancel을 위한 job 추가
    
    fun consumeEach(uiBody: (item: E) -> Unit): CoroutinesSendChannelOnClickEvent<E> {
        val clickActor = CoroutineScope(dispatcherProvider.main + (job ?: EmptyCoroutineContext)).actor<View> {
            this.channel.map(context = dispatcherProvider.default, transform = bgBody).consumeEach(uiBody)
        }
        view.setOnClickListener { clickActor.offer(it) }	// Offer 처리를 위한 CoroutineScope 생성
        return this
    }
}
fun <E> View.onClick(dispatcherProvider: DispatchersProviderSealed = DispatchersProvider,
                     job: Job? = null, bgBody: suspend (item: View) -> E): CoroutinesSendChannelOnClickEvent<E> =
CoroutinesSendChannelOnClickEvent(this, bgBody, dispatcherProvider, job)

infix fun <E> CoroutinesSendChannelOnClickEvent<E>.consume(uiBody: (item: E) -> Unit) {	// 생성을 간단하게 하기 위한 function 2개
    this.consumeEach(uiBody)
}
使い方は以下の通りです.
fab.onClick(job = job) {		// click을 처리하고, background에서 loadnetwork()
    loadNetwork()
} consume {
    tv_message.text = it
}

private suspend fun loadNetwork(): String {		// Temp load network
    delay(300)
    return "currentIndex ${currentIndex++}"
}

長所


既存のRx上のThrottleFirstを使用する場合、ビューごとのクリックイベントの時間を正確に判断できないため、500 ms->などの任意の時間を指定できます.これはコードインスタンスで解決できます.