11package com.instacart.formula.android
22
3- import com.instacart.formula.RuntimeConfig
43import com.instacart.formula.android.events.FragmentLifecycleEvent
54import com.instacart.formula.android.internal.FeatureComponent
65import com.instacart.formula.android.internal.Features
7- import com.instacart.formula.android.internal.FragmentStoreFormula
86import com.instacart.formula.android.utils.MainThreadDispatcher
9- import com.instacart.formula.rxjava3.toObservable
7+ import com.jakewharton.rxrelay3.PublishRelay
108import io.reactivex.rxjava3.core.Observable
9+ import io.reactivex.rxjava3.disposables.Disposable
1110
1211/* *
1312 * A FragmentStore is responsible for managing the state of multiple [FragmentKey] instances.
1413 */
1514class FragmentStore @PublishedApi internal constructor(
16- private val formula : FragmentStoreFormula ,
15+ private val featureComponent : FeatureComponent < * > ,
1716) {
1817 companion object {
1918 val EMPTY = init { }
@@ -37,23 +36,116 @@ class FragmentStore @PublishedApi internal constructor(
3736 features : Features <Component >
3837 ): FragmentStore {
3938 val featureComponent = FeatureComponent (component, features.bindings)
40- val formula = FragmentStoreFormula (featureComponent)
41- return FragmentStore (formula)
39+ return FragmentStore (featureComponent)
4240 }
4341 }
4442
43+ @Volatile private var disposed = false
44+ private val dispatcher = MainThreadDispatcher ()
45+ private val updateRelay = PublishRelay .create<Unit >()
46+ private var state = FragmentState ()
47+ private var runningFeatures = mutableMapOf<FragmentId , Disposable >()
48+
49+ private lateinit var fragmentEnvironment: FragmentEnvironment
50+
4551 internal fun onLifecycleEffect (event : FragmentLifecycleEvent ) {
46- formula.onLifecycleEffect(event)
52+ val fragmentId = event.fragmentId
53+ when (event) {
54+ is FragmentLifecycleEvent .Added -> handleNewFragment(fragmentId)
55+ is FragmentLifecycleEvent .Removed -> handleRemoveFragment(fragmentId)
56+ }
4757 }
4858
4959 internal fun onVisibilityChanged (fragmentId : FragmentId , visible : Boolean ) {
50- formula.onVisibilityChanged(fragmentId, visible)
60+ dispatcher.dispatch {
61+ val visibleIds = if (visible) {
62+ state.visibleIds.plus(fragmentId)
63+ } else {
64+ state.visibleIds.minus(fragmentId)
65+ }
66+ state = state.copy(visibleIds = visibleIds)
67+ updateRelay.accept(Unit )
68+ }
5169 }
5270
71+ // TODO: should not be an observable.
5372 internal fun state (environment : FragmentEnvironment ): Observable <FragmentState > {
54- val config = RuntimeConfig (
55- defaultDispatcher = MainThreadDispatcher (),
56- )
57- return formula.toObservable(environment, config)
73+ // TODO: should be set differently
74+ fragmentEnvironment = environment
75+ return updateRelay.startWithItem(Unit ).map { state }.distinctUntilChanged()
76+ }
77+
78+ fun dispose () {
79+ disposed = true
80+
81+ dispatcher.dispatch {
82+ for (running in runningFeatures) {
83+ running.value.dispose()
84+ }
85+
86+ runningFeatures.clear()
87+ }
88+ }
89+
90+ private fun handleNewFragment (fragmentId : FragmentId ) {
91+ dispatcher.dispatch {
92+ if (disposed) return @dispatch
93+ if (state.activeIds.contains(fragmentId)) return @dispatch
94+
95+ val featureEvent = featureComponent.init (fragmentEnvironment, fragmentId)
96+ state = state.copy(
97+ activeIds = state.activeIds.plus(fragmentId),
98+ features = state.features.plus(featureEvent.id to featureEvent)
99+ )
100+
101+ runFeature(fragmentEnvironment, featureEvent)
102+
103+ updateRelay.accept(Unit )
104+ }
105+ }
106+
107+ private fun handleRemoveFragment (fragmentId : FragmentId ) {
108+ dispatcher.dispatch {
109+ if (disposed) return @dispatch
110+
111+ runningFeatures.remove(fragmentId)?.dispose()
112+
113+ if (state.activeIds.contains(fragmentId)) {
114+ state = state.copy(
115+ activeIds = state.activeIds.minus(fragmentId),
116+ features = state.features.minus(fragmentId),
117+ outputs = state.outputs.minus(fragmentId),
118+ )
119+ updateRelay.accept(Unit )
120+ }
121+ }
122+ }
123+
124+ private fun runFeature (environment : FragmentEnvironment , event : FeatureEvent ) {
125+ val fragmentId = event.id
126+ val feature = (event as ? FeatureEvent .Init )?.feature
127+ if (feature != null ) {
128+ val observable = feature.stateObservable.onErrorResumeNext {
129+ environment.onScreenError(fragmentId.key, it)
130+ Observable .empty()
131+ }
132+
133+ runningFeatures[fragmentId] = observable.subscribe {
134+ publishUpdate(fragmentId, it)
135+ }
136+ }
137+ }
138+
139+ private fun publishUpdate (fragmentId : FragmentId , output : Any ) {
140+ dispatcher.dispatch {
141+ if (! state.activeIds.contains(fragmentId)) {
142+ return @dispatch
143+ }
144+
145+ val keyState = FragmentOutput (fragmentId.key, output)
146+ state = state.copy(outputs = state.outputs.plus(fragmentId to keyState))
147+
148+ updateRelay.accept(Unit )
149+ }
58150 }
59151}
0 commit comments