Kotlin coroutine flow example for Android button click event?
Asked Answered
B

2

10

I used to use Channel to send out click event from Anko View class to Activity class, however more and more Channel functions are marked as deprecated. So I wanted to start using Flow apis.

I migrated code below:

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) 
       }
    }
}

I have to mark flow properties as var now which is not so elegant as before. Is this way right? Can I init a Rx Subject like Flow when defining the property?


Edit:

I brought Channel back, then used 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

This seems better than lateinit var one, but any way to get rid of Channel completely? (Though Flow itself like callbackFlow, channelFlow are using channel)

Barely answered 12/9, 2019 at 3:26 Comment(0)
D
23

Although I don't use Anko in my project, I've written this function to use with regular button references, see if it helps you:

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

An example of possible usage is:

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

UPDATE

As @Micer mentioned in the comments to the original answer, the method Channel#offer has become deprecated in the favour of Channel#trySend method.

The updated version:

fun View.clicks() = callbackFlow<Unit> {
    setOnClickListener {
        trySend(Unit)
    }
    awaitClose { setOnClickListener(null)}
}
Daffi answered 12/9, 2019 at 20:57 Comment(5)
Thanks for sharing this useful kotlin extension! This replaces my old RxView.clicks(view) :)Kiowa
What is the difference between lifecycleScope.launch and .launchIn(lifecycleScope)?Orpiment
guys for some reasona it's not working on AppcompatButtonGlobuliferous
@Orpiment aside from being more convenient in some cases, it's a terminal statement, almost always needs onEach() beforeSolution
offer is deprecated and should be replaced by trySendDropper
R
0

For kotlin lover

Using callbacFlow

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

Usage

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

Using 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 }
}

Usage

bind.btn.setOnClick {
    // do your staff
  }
Rikkiriksdag answered 1/3, 2022 at 4:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.