簡體   English   中英

如何將項目發送到 Kotlin.Flow(如 Behaviorsubject)

[英]How can I send items to a Kotlin.Flow (like a Behaviorsubject)

我想知道如何向Kotlin.Flow發送/發送項目,所以我的用例是:

在消費者/ViewModel/Presenter 中,我可以使用collect函數進行訂閱

fun observe() {
 coroutineScope.launch {
    // 1. Send event
    reopsitory.observe().collect {
      println(it)
    }
  }
}

但問題在於Repository方面,使用 RxJava,我們可以使用Behaviorsubject將其公開為Observable/Flowable並發出如下新項目:

behaviourSubject.onNext(true)

但是每當我建立一個新流程時:

flow {

}

我只能收集 如何將值發送到流?

如果您想獲得訂閱/收藏的最新值,您應該使用ConflatedBroadcastChannel

private val channel = ConflatedBroadcastChannel<Boolean>()

這將復制BehaviourSubject ,以將通道公開為流:

// Repository
fun observe() {
  return channel.asFlow()
}

現在向暴露的Flow發送一個事件/值,簡單地發送到這個通道。

// Repository
fun someLogicalOp() {
  channel.send(false) // This gets sent to the ViewModel/Presenter and printed.
}

安慰:

錯誤的

如果您希望僅開始收集接收值則應改用BroadcastChannel

要說清楚:

表現為 Rx 的PublishedSubject

private val channel = BroadcastChannel<Boolean>(1)

fun broadcastChannelTest() {
  // 1. Send event
  channel.send(true)

  // 2. Start collecting
  channel
    .asFlow()
    .collect {
      println(it)
    }

  // 3. Send another event
  channel.send(false)
}

錯誤的

當第一個事件collect { }之前發送時只會打印false


表現為 Rx 的BehaviourSubject

private val confChannel = ConflatedBroadcastChannel<Boolean>()

fun conflatedBroadcastChannelTest() {
  // 1. Send event
  confChannel.send(true)

  // 2. Start collecting
  confChannel
    .asFlow()
    .collect {
      println(it)
    }

  // 3. Send another event
  confChannel.send(false)
}

真的

錯誤的

這兩個事件都被打印出來,你總是得到最新的值(如果有的話)。

另外,想提一下 Kotlin 在DataFlow上的團隊開發(名稱待定):

這似乎更適合這個用例(因為它將是一個冷流)。

看看MutableStateFlow文檔,因為它是ConflatedBroadcastChannel的替代品,很快就會被棄用。

要獲得更好的上下文,請查看關於 Github 上 Kotlin 存儲庫原始問題整個討論

更新

Kotlin Coroutines 1.4.0現在可用於MutableSharedFlow ,它取代了對Channel的需要。 MutableSharedFlow清理也是內置的,所以你不需要手動打開和關閉它,不像Channel 如果您需要類似主題的 api 用於Flow請使用MutableSharedFlow

原答案

由於您的問題有android標記,我將添加一個 Android 實現,允許您輕松創建處理其自身生命周期的BehaviorSubjectPublishSubject

這在 Android 中很重要,因為您不想忘記關閉通道並泄漏內存。 此實現通過將反應流與 Fragment/Activity 的創建和銷毀聯系起來,避免了顯式“處置”反應流的需要。 類似於LiveData

interface EventReceiver<Message> {
    val eventFlow: Flow<Message>
}

interface EventSender<Message> {
    fun postEvent(message: Message)
    val initialMessage: Message?
}

class LifecycleEventSender<Message>(
    lifecycle: Lifecycle,
    private val coroutineScope: CoroutineScope,
    private val channel: BroadcastChannel<Message>,
    override val initialMessage: Message?
) : EventSender<Message>, LifecycleObserver {

    init {
        lifecycle.addObserver(this)
    }

    override fun postEvent(message: Message) {
        if (!channel.isClosedForSend) {
            coroutineScope.launch { channel.send(message) }
        } else {
            Log.e("LifecycleEventSender","Channel is closed. Cannot send message: $message")
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun create() {
        channel.openSubscription()
        initialMessage?.let { postEvent(it) }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun destroy() {
        channel.close()
    }
}

class ChannelEventReceiver<Message>(channel: BroadcastChannel<Message>) :
    EventReceiver<Message> {
    override val eventFlow: Flow<Message> = channel.asFlow()
}

abstract class EventRelay<Message>(
    lifecycle: Lifecycle,
    coroutineScope: CoroutineScope,
    channel: BroadcastChannel<Message>,
    initialMessage: Message? = null
) : EventReceiver<Message> by ChannelEventReceiver<Message>(channel),
    EventSender<Message> by LifecycleEventSender<Message>(
        lifecycle,
        coroutineScope,
        channel,
        initialMessage
    )

通過使用 Android 的Lifecycle庫,我現在可以創建一個BehaviorSubject ,在活動/片段被銷毀后自行清理

class BehaviorSubject<String>(
    lifecycle: Lifecycle,
    coroutineScope: CoroutineScope,
    initialMessage = "Initial Message"
) : EventRelay<String>(
    lifecycle,
    coroutineScope,
    ConflatedBroadcastChannel(),
    initialMessage
)

或者我可以使用緩沖的BroadcastChannel創建一個PublishSubject

class PublishSubject<String>(
    lifecycle: Lifecycle,
    coroutineScope: CoroutineScope,
    initialMessage = "Initial Message"
) : EventRelay<String>(
    lifecycle,
    coroutineScope,
    BroadcastChannel(Channel.BUFFERED),
    initialMessage
)

現在我可以做這樣的事情

class MyActivity: Activity() {

    val behaviorSubject = BehaviorSubject(
        this@MyActivity.lifecycle,
        this@MyActivity.lifecycleScope
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (savedInstanceState == null) { 

            behaviorSubject.eventFlow
                .onEach { stringEvent ->
                    Log.d("BehaviorSubjectFlow", stringEvent)
                    // "BehaviorSubjectFlow: Initial Message"
                    // "BehaviorSubjectFlow: Next Message"
                }
                .flowOn(Dispatchers.Main)
                .launchIn(this@MyActivity.lifecycleScope)

        }
    }

    override fun onResume() {
        super.onResume()

        behaviorSubject.postEvent("Next Message")
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM