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)

Zouyiu Ng
  • 193
  • 1
  • 12

2 Answers2

22

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)}
}
Stanislav Shamilov
  • 1,746
  • 11
  • 20
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
  }
Tarif Chakder
  • 1,708
  • 1
  • 11
  • 10