Я пытаюсь понять, как реализовать что-то в RxJava (2.0). Это для Android, и я использую Kotlin, хотя выбор платформы и языка здесь не должен иметь значения.
Идея состоит в том, что я бы основал какую-то архитектуру MVP на RxJava. В этой реализации я думаю о том, что Activity
(может быть Fragment
или пользовательское View
) предоставляет поток значений (Boolean
s для простоты), которые указывают на события жизненного цикла или на то, прикреплено ли представление или отсоединено.
Основная идея в основном такова:
private val lifecycleEvents = PublishSubject.create<Boolean>()
val screenStates: Observable<Boolean> = lifecycleEvents.hide()
override fun onResume() {
super.onResume()
lifecycleEvents.onNext(true) // I'm attached!
}
override fun onPause() {
lifecycleEvents.onNext(false) // I'm detached!
super.onPause()
}
override fun onDestroy() {
lifecycleEvents.onComplete() // I'm gone
super.onDestroy()
}
А затем с другого конца Presenter выставляет Observable
, который представляет собой поток объектов, представляющих состояния экрана, которые должны быть отображены View.
(Это соответствует концепции, описанной в этой серии http://hannesdorfmann.com/android/mosby3-mvi-1, что сводится к тому факту, что Presenter передает представление с автономными объектами, полностью инкапсулирующими состояния экрана, а не с несколькими различными методами представления).
И затем я хотел бы связать эти два наблюдаемых потока, чтобы:
Всякий раз, когда View отсоединяется, ввод от Presenter игнорируется (и он не буферизуется, чтобы не сталкиваться с проблемами обратного давления)
Однако после повторного подключения представления оно получает последнее состояние, отправленное докладчиком. Другими словами, буферизовать следует не более одного экземпляра состояния.
Это будет работать следующим образом (предполагая, что состояния имеют тип String
для простоты):
val merged: Observable<String> = ???
val attached = true
val disattached = false
screenStates.onNext(attached)
fromPresenter.onNext("state A")
fromPresenter.onNext("state B")
screenStates.onNext(disattached)
fromPresenter.onNext("state C") // this won't survive at the end
fromPresenter.onNext("state D") // this will "override" the previous one.
// as that's the last state from BEFORE the screen is reattached
screenStates.onNext(attached)
// "state D" should be replayed at this point, "state C" is skipped and lost
fromPresenter.onNext("state E")
// what "merged" is supposed to have received at this point:
// "state A", "state B", "state D", "state E"
Я не уверен, какое лучшее идиоматическое решение.
Я пытался реализовать это как ObservableTransformer
, но у меня не получилось. Я считаю, что преобразователь должен быть без гражданства, тогда как мое решение тяготело к явному отслеживанию того, что было испущено, и буферизации последнего элемента «вручную» и т. Д., Что кажется грязным и слишком императивным, поэтому я полагаю, что это неправильно.
Я нашел https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableValve.java, но реализация выглядит очень сложной, и я не могу поверить, что это могло не сделать проще (мне не нужна вся гибкость, я хочу только то, что работает для описанного варианта использования).
Буду признателен за любую информацию, в том числе о том, есть ли что-то еще, что я должен принять во внимание в контексте Android. Также обратите внимание, что я не использую привязки RxKotlin (могу, я просто не предполагал, что они здесь потребуются).
ИЗМЕНИТЬ:
Ниже моя текущая реализация. Как я уже сказал, я не слишком доволен этим, потому что это явное сохранение состояния, и я считаю, что это должно быть достигнуто декларативно, используя некоторые конструкции RxJava.
Мне нужно было объединить два потока разных типов, и поскольку ни combineLatest
, ни zip
не совсем справлялись, я прибегнул к хитрости, создав общую оболочку для обоих разных типов событий. Это снова вводит определенные накладные расходы.
sealed class Event
class StateEvent(val state: String): Event()
class LifecycleEvent(val attached: Boolean): Event()
class ValveTransformer(val valve: Observable<Boolean>) : ObservableTransformer<String, String> {
var lastStateEvent: Event? = null
var lastLifecycleEvent = LifecycleEvent(false)
private fun buffer(event: StateEvent) {
lastStateEvent = event
}
private fun buffer(event: LifecycleEvent) {
lastLifecycleEvent = event
}
private fun popLastState(): String {
val bufferedState = (lastStateEvent as StateEvent).state
lastStateEvent = null
return bufferedState
}
override fun apply(upstream: Observable<String>): ObservableSource<String> = Observable
.merge(
upstream.map(::StateEvent).doOnNext { buffer(it) },
valve.distinctUntilChanged().map(::LifecycleEvent).doOnNext { buffer (it) })
.switchMap { when {
it is LifecycleEvent && it.attached && lastStateEvent != null ->
// the screen is attached now, pump the pending state out of the buffer
just(popLastState())
it is StateEvent && lastLifecycleEvent.attached -> just(it.state)
else -> empty<String>()
} }
}