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,112 @@ 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+ if (state.activeIds.contains(fragmentId)) {
112+ state = state.copy(activeIds = state.activeIds.minus(fragmentId))
113+ runningFeatures[fragmentId]?.dispose()
114+ runningFeatures.remove(fragmentId)
115+ updateRelay.accept(Unit )
116+ }
117+ }
118+ }
119+
120+ private fun runFeature (environment : FragmentEnvironment , event : FeatureEvent ) {
121+ val fragmentId = event.id
122+ val feature = (event as ? FeatureEvent .Init )?.feature
123+ if (feature != null ) {
124+ val observable = feature.stateObservable.onErrorResumeNext {
125+ environment.onScreenError(fragmentId.key, it)
126+ Observable .empty()
127+ }
128+
129+ runningFeatures[fragmentId] = observable.subscribe {
130+ publishUpdate(fragmentId, it)
131+ }
132+ }
133+ }
134+
135+ private fun publishUpdate (fragmentId : FragmentId , output : Any ) {
136+ dispatcher.dispatch {
137+ if (! state.activeIds.contains(fragmentId)) {
138+ return @dispatch
139+ }
140+
141+ val keyState = FragmentOutput (fragmentId.key, output)
142+ state = state.copy(outputs = state.outputs.plus(fragmentId to keyState))
143+
144+ updateRelay.accept(Unit )
145+ }
58146 }
59147}
0 commit comments