Kotlin coroutine flow example for Android button click event?

8.8k views Asked by At

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)

2

There are 2 answers

5
Stanislav Shamilov On BEST ANSWER

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)}
}
0
Tarif Chakder On

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
  }