Android 按钮单击事件的 Kotlin 协程流示例?
Kotlin coroutine flow example for Android button click event?
我曾经使用Channel
将点击事件从Anko View class发送到Activity class,但是越来越多的Channel
函数是标记为已弃用。所以我想开始使用 Flow
api。
我迁移了以下代码:
private val btnProduceChannel = Channel<Unit>()
val btnChannel : ReceiveChannel<Unit> = btnProduceChannel
// Anko
button {
onClick {
btnProduceChannel.send(Unit)
}
}
至:
lateinit var btnFlow: Flow<Unit>
private set
button {
btnFlow = flow {
onClick {
emit(Unit)
}
}
}
我现在必须将流属性标记为 var
,这不像以前那么优雅。这种方式对吗?我可以在定义 属性 时像 Flow
一样初始化 Rx Subject
吗?
编辑:
我把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
? (虽然Flow
本身和callbackFlow
一样,channelFlow
也在使用频道)
虽然我没有在我的项目中使用 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)}
}
科特林爱好者
使用callbacFlow
fun View.clicks() = callbackFlow {
setOnClickListener {
this.trySend(Unit).isSuccess
}
awaitClose { setOnClickListener(null) }
}
用法
bind.btn.clicks().onEach {
// do your staff
}.launchIn(lifecycleScope)
使用Channel
eventActor
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
}
我曾经使用Channel
将点击事件从Anko View class发送到Activity class,但是越来越多的Channel
函数是标记为已弃用。所以我想开始使用 Flow
api。
我迁移了以下代码:
private val btnProduceChannel = Channel<Unit>()
val btnChannel : ReceiveChannel<Unit> = btnProduceChannel
// Anko
button {
onClick {
btnProduceChannel.send(Unit)
}
}
至:
lateinit var btnFlow: Flow<Unit>
private set
button {
btnFlow = flow {
onClick {
emit(Unit)
}
}
}
我现在必须将流属性标记为 var
,这不像以前那么优雅。这种方式对吗?我可以在定义 属性 时像 Flow
一样初始化 Rx Subject
吗?
编辑:
我把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
? (虽然Flow
本身和callbackFlow
一样,channelFlow
也在使用频道)
虽然我没有在我的项目中使用 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)}
}
科特林爱好者
使用callbacFlow
fun View.clicks() = callbackFlow {
setOnClickListener {
this.trySend(Unit).isSuccess
}
awaitClose { setOnClickListener(null) }
}
用法
bind.btn.clicks().onEach {
// do your staff
}.launchIn(lifecycleScope)
使用Channel
eventActor
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
}