首页 > 解决方案 > 有没有办法在 Kotlin 中使用 coroutines/Flow/Channels 实现这个 rx 流?

问题描述

我第一次尝试 Kotlin Coroutines 和 Flow,我试图用 MVI-ish 方法重现我在带有 RxJava 的 Android 上使用的某个流,但是我很难让它正确,我基本上被困在这一点上.

RxJava 应用程序看起来基本上是这样的:

主活动视图.kt

object MainActivityView {

    sealed class Event {
        object OnViewInitialised : Event()
    }

    data class State(
        val renderEvent: RenderEvent = RenderEvent.None
    )

    sealed class RenderEvent {
        object None : RenderEvent()
        class DisplayText(val text: String) : RenderEvent()
    }
}

MainActivity.kt

MainActivity 有一个PublishSubject带有Event类型的实例。即MainActivityView.Event.OnViewInitialisedMainActivityView.Event.OnError。初始事件是onCreate()通过主体的.onNext(Event)调用发送的。

@MainActivityScope
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    @Inject
    lateinit var subscriptions: CompositeDisposable

    @Inject
    lateinit var viewModel: MainActivityViewModel

    @Inject
    lateinit var onViewInitialisedSubject: PublishSubject<MainActivityView.Event.OnViewInitialised>

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

    override fun onDestroy() {
        super.onDestroy()
        subscriptions.clear()
    }

    private fun setupEvents() {
        if (subscriptions.size() == 0) {
            Observable.mergeArray(
                onViewInitialisedSubject
                    .toFlowable(BackpressureStrategy.BUFFER)
                    .toObservable()
            ).observeOn(
                Schedulers.io()
            ).compose(
                viewModel()
            ).observeOn(
                AndroidSchedulers.mainThread()
            ).subscribe(
                ::render
            ).addTo(
                subscriptions
            )

            onViewInitialisedSubject
                .onNext(
                    MainActivityView
                        .Event
                        .OnViewInitialised
                )
        }
    }

    private fun render(state: MainActivityView.State) {
        when (state.renderEvent) {
            MainActivityView.RenderEvent.None -> Unit
            is MainActivityView.RenderEvent.DisplayText -> {
                mainActivityTextField.text = state.renderEvent.text
            }
        }
    }

}

MainActivityViewModel.kt

然后,这些被调用Event的类拾取,然后将接收到的转换为一种新的via 。视图模型返回一个带有 a 的新状态,然后可以在再次通过函数中对其进行操作。MainActivityViewModel.compose(viewModel())EventStateObservableTransformer<Event, State>renderEventMainActivityrender(state: MainActivityView.State)

@MainActivityScope
class MainActivityViewModel @Inject constructor(
    private var state: MainActivityView.State
) {

    operator fun invoke(): ObservableTransformer<MainActivityView.Event, MainActivityView.State> = onEvent

    private val onEvent = ObservableTransformer<MainActivityView.Event,
        MainActivityView.State> { upstream: Observable<MainActivityView.Event> ->
        upstream.publish { shared: Observable<MainActivityView.Event> ->
            Observable.mergeArray(
                shared.ofType(MainActivityView.Event.OnViewInitialised::class.java)
            ).compose(
                eventToViewState
            )
        }
    }

    private val eventToViewState = ObservableTransformer<MainActivityView.Event, MainActivityView.State> { upstream ->
        upstream.flatMap { event ->
            when (event) {
                MainActivityView.Event.OnViewInitialised -> onViewInitialisedEvent()
            }
        }
    }

    private fun onViewInitialisedEvent(): Observable<MainActivityView.State> {
        val renderEvent = MainActivityView.RenderEvent.DisplayText(text = "hello world")
        state = state.copy(renderEvent = renderEvent)
        return state.asObservable()
    }

}

我可以使用协程/流/通道实现相同的流程吗?甚至可能有点简化?

编辑:

从那以后,我找到了适合我的解决方案,到目前为止我还没有发现任何问题。然而,这个解决方案ConflatedBroadcastChannel<T>最终将被弃用,它可能会用(在撰写本文时)尚未发布的SharedFlowapi 替换它(更多内容在这里.

它的工作方式是Activity和 viewmodel 共享一个ConflatedBroadcastChannel<MainActivity.Event>用于从Activity(或适配器)发送或提供事件的。视图模型将事件减少到一个新的状态,然后发出。Activity正在收集Flow<State>返回的,viewModel.invoke()并最终呈现发出的State

主活动视图.kt

object MainActivityView {

    sealed class Event {
        object OnViewInitialised : Event()
        data class OnButtonClicked(val idOfItemClicked: Int) : Event()
    }

    data class State(
        val renderEvent: RenderEvent = RenderEvent.Idle
    )

    sealed class RenderEvent {
        object Idle : RenderEvent()
        data class DisplayText(val text: String) : RenderEvent()
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity(R.layout.activity_main) {

    @Inject
    lateinit var viewModel: MainActivityViewModel

    @Inject
    lateinit eventChannel: ConflatedBroadcastChannel<MainActivityView.Event>

    private var isInitialised: Boolean = false

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

        init()
    }
    
    private fun init() {
        if (!isInitialised) {
            
            lifecycleScope.launch {
                viewModel()
                    .flowOn(
                        Dispatchers.IO
                    ).collect(::render)
            }

            eventChannel
                .offer(
                    MainActivityView.Event.OnViewInitialised
                )
            isInitialised = true
        }
    }

    private suspend fun render(state: MainActivityView.State): Unit =
        when (state.renderEvent) {
            MainActivityView.RenderEvent.Idle -> Unit
            is MainActivityView.RenderEvent.DisplayText -> 
                renderDisplayText(text = state.renderEvent.text)
            
        }

    private val renderDisplayText(text: String) {
        // render text
    }

}

MainActivityViewModel.kt

class MainActivityViewModel constructor(
    private var state: MainActivityView.State = MainActivityView.State(),
    private val eventChannel: ConflatedBroadcastChannel<MainActivityView.Event>,
 ) {

    suspend fun invoke(): Flow<MainActivityView.State> =
        eventChannel
            .asFlow()
            .flatMapLatest { event: MainActivityView.Event ->
                reduce(event)
            }

    private fun reduce(event: MainActivityView.Event): Flow<MainActivityView.State> =
        when (event) {
            MainActivityView.Event.OnViewInitialised -> onViewInitialisedEvent()
            MainActivityView.Event.OnButtonClicked -> onButtonClickedEvent(event.idOfItemClicked)
        }

    private fun onViewInitialisedEvent(): Flow<MainActivityView.State> = flow 
        val renderEvent = MainActivityView.RenderEvent.DisplayText(text = "hello world")
        state = state.copy(renderEvent = renderEvent)
        emit(state)
    }

    private fun onButtonClickedEvent(idOfItemClicked: Int): Flow<MainActivityView.State> = flow 
        // do something to handle click
        println("item clicked: $idOfItemClicked")
        emit(state)
    }

}

类似问题:

标签: androidkotlinkotlin-coroutines

解决方案


MainActivity可以看起来像这样。

@MainActivityScope
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    @Inject
    lateinit var subscriptions: CompositeDisposable

    @Inject
    lateinit var viewModel: MainActivityViewModel

    @Inject
    lateinit var onViewInitialisedChannel: BroadcastChannel<MainActivityView.Event.OnViewInitialised>

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

    override fun onDestroy() {
        super.onDestroy()
        subscriptions.clear()
    }

    private fun setupEvents() {
        if (subscriptions.size() == 0) {
            onViewInitialisedChannel.asFlow()
                .buffer()
                .flowOn(Dispatchers.IO)
                .onEach(::render)
                .launchIn(GlobalScope)

            onViewInitialisedChannel
                .offer(
                    MainActivityView
                        .Event
                        .OnViewInitialised
                )
        }
    }

    private fun render(state: MainActivityView.State) {
        when (state.renderEvent) {
            MainActivityView.RenderEvent.None -> Unit
            is MainActivityView.RenderEvent.DisplayText -> {
                mainActivityTextField.text = state.renderEvent.text
            }
        }
    }

}

推荐阅读