Android按钮点击事件的Kotlin协程流示例?

10

我曾经使用Channel从Anko View类发送点击事件到Activity类,但是越来越多的Channel函数被标记为过时。因此,我想开始使用Flow API。

我迁移了下面的代码:

private val btnProduceChannel = Channel<Unit>()
val btnChannel : ReceiveChannel<Unit> = btnProduceChannel 

// Anko
button {
    onClick {
        btnProduceChannel.send(Unit)
    }
}

to:

lateinit var btnFlow: Flow<Unit>
    private set

button {
    btnFlow = flow { 
       onClick { 
            emit(Unit) 
       }
    }
}

现在我必须将流属性标记为var,这不像以前那样优雅。这种方式正确吗?当定义属性时,我可以像初始化Rx Subject一样初始化Flow吗?


编辑:

我重新使用了Channel,然后使用了consumeAsFlow()

private val btnChannel = Channel<Unit>()

// This can be collected only once
val btnFlow = btnChannel.consumeAsFlow()

// Or add get() to make property can be collected multiple times
// But the "get()" can be easily forgotten and I don't know the performance of create flow every access
val btnFlow get() = btnChannel.consumeAsFlow()


// Send event with btnChannel

这似乎比lateinit var更好,但有没有完全摆脱Channel的方法?(尽管像callbackFlowchannelFlow这样的Flow本身也在使用通道。)
2个回答

22

虽然我在我的项目中没有使用Anko,但我编写了这个函数来与普通的按钮引用一起使用,看看它是否对你有帮助:

fun View.clicks(): Flow<Unit> = callbackFlow {
    setOnClickListener {
        offer(Unit)
    }
    awaitClose { setOnClickListener(null) }
}

一个可能的使用示例是:

button.clicks()
   .onEach { /*React on a click event*/ }
   .launchIn(lifecycleScope)

更新

正如@Micer在原回答的评论中提到的那样,Channel#offer方法已经被弃用,取而代之的是Channel#trySend方法。

更新后的版本:

fun View.clicks() = callbackFlow<Unit> {
    setOnClickListener {
        trySend(Unit)
    }
    awaitClose { setOnClickListener(null)}
}

感谢分享这个有用的 Kotlin 扩展!这取代了我以前的 RxView.clicks(view) :) - Ryan Amaral
lifecycleScope.launch.launchIn(lifecycleScope) 有什么区别? - IgorGanapolsky
伙计们,由于某些原因,AppcompatButton 上不起作用。 - vikas kumar
1
@IgorGanapolsky 除了在某些情况下更方便之外,它还是一个终端语句,几乎总是需要在其前面加上 onEach() - Ace
offer is deprecated and should be replaced by trySend - Micer

0

对于喜爱 Kotlin 的人

使用 callbacFlow

fun View.clicks() = callbackFlow {
setOnClickListener {
    this.trySend(Unit).isSuccess
}
 awaitClose { setOnClickListener(null) }
}

使用方法

bind.btn.clicks().onEach {
 // do your staff
}.launchIn(lifecycleScope)

使用 Channel 事件执行器
fun View.setOnClick(action: suspend () -> Unit) {
 // launch one actor as a parent of the context job
 val scope = (context as? CoroutineScope) ?: AppScope
 val eventActor = scope.actor<Unit>(capacity = Channel.CONFLATED) {
     for (event in channel) action()
  }
    // install a listener to activate this actor
    setOnClickListener { eventActor.trySend(Unit).isSuccess }
}

用法

bind.btn.setOnClick {
    // do your staff
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接