android - 有没有办法在 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.OnViewInitialised
等MainActivityView.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())
Event
State
ObservableTransformer<Event, State>
renderEvent
MainActivity
render(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>
最终将被弃用,它可能会用(在撰写本文时)尚未发布的SharedFlow
api 替换它(更多内容在这里.
它的工作方式是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)
}
}
类似问题:
解决方案
你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
}
}
}
}
推荐阅读
- python - 部分代码未执行(未显示错误)
- java - 如何使用jackson java向数组节点中的每个对象添加附加字段?
- serverless - Polybase 外部表与 OPENROWSET 无服务器 sql 池架构
- python - 从 Python 中的 apache_beam DoFn 以镶木地板格式写入 GCS
- kotlin - 抽象类可以在 Kotlin 中包含非抽象类参数吗?
- tcp - 这个 TCP 拥塞控制图中发生了什么?
- php - 将foreach循环的值保存在php中的本地初始化数组中
- python - 使用 Tkinter 功能创建多个下拉菜单并允许用户选择和显示不同的选项
- node.js - 使用预签名 URL 流式上传到 S3
- parsing - 解析飞镖 | ANTLR | 处理参数列表末尾的逗号