Skip to content

Commit

Permalink
Rename FragmentStoreBuilder to FeaturesBuilder.
Browse files Browse the repository at this point in the history
  • Loading branch information
Laimiux committed Sep 4, 2024
1 parent d60112a commit db3b8f0
Show file tree
Hide file tree
Showing 17 changed files with 65 additions and 79 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
- Enable fine-grained control of dispatching via `Plugin.defaultDispatcher`, `RuntimeConfig.defaultDispatcher`, `Transition.ExecutionType` and `Effect.Type`.
- Remove `RxStream` type alias.
- Replace `implementation` function with a value
- Removed `FlowFactory` and `Flow`
- **Breaking**: Removed `FlowFactory` and `Flow`
- **Breaking**: Replace `FragmentStoreBuilder` with `FeaturesBuilder`

## [0.7.1] - June 28, 2022
- **Breaking**: Rename `FragmentBindingBuilder` to `FragmentStoreBuilder`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.instacart.formula.android.Feature
import com.instacart.formula.android.FeatureFactory
import com.instacart.formula.android.FragmentFlowStore
import com.instacart.formula.android.ViewFactory
import com.instacart.formula.android.events.ActivityResult
import com.instacart.formula.test.TestFragmentActivity
Expand All @@ -29,7 +30,7 @@ class FragmentAndroidEventTest {
configureActivity = {
initialContract = TestLifecycleKey()
},
contracts = {
fragmentStore = FragmentFlowStore.init {
val featureFactory = object : FeatureFactory<Unit, TestLifecycleKey> {
override fun initialize(dependencies: Unit, key: TestLifecycleKey): Feature {
return Feature(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.google.common.truth.Truth.assertThat
import com.instacart.formula.android.FragmentFlowState
import com.instacart.formula.android.FragmentKey
import com.instacart.formula.android.BackCallback
import com.instacart.formula.android.FragmentFlowStore
import com.instacart.formula.test.TestKey
import com.instacart.formula.test.TestKeyWithId
import com.instacart.formula.test.TestFragmentActivity
Expand Down Expand Up @@ -49,7 +50,7 @@ class FragmentFlowRenderViewTest {

updateThreads.add(Thread.currentThread())
},
contracts = {
fragmentStore = FragmentFlowStore.init {
bind(TestFeatureFactory<TestKey> { stateChanges(it) })
bind(TestFeatureFactory<TestKeyWithId> { stateChanges(it) })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.google.common.truth.Truth.assertThat
import com.instacart.formula.android.FormulaFragment
import com.instacart.formula.android.ActivityStoreContext
import com.instacart.formula.android.FeatureFactory
import com.instacart.formula.android.FragmentFlowStore
import com.instacart.formula.android.FragmentKey
import com.instacart.formula.test.TestKey
import com.instacart.formula.test.TestKeyWithId
Expand Down Expand Up @@ -36,7 +37,7 @@ class FragmentLifecycleStateTest {
configureActivity = {
initialContract = TestKey()
},
contracts = {
fragmentStore = FragmentFlowStore.init {
bind(featureFactory<TestKey>(this@activity))
bind(featureFactory<TestKeyWithId>(this@activity))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.instacart.formula.android.Feature
import com.instacart.formula.android.FeatureFactory
import com.instacart.formula.android.FragmentFlowStore
import com.instacart.formula.android.ViewFactory
import com.instacart.formula.test.TestFragmentActivity
import com.instacart.formula.test.TestFragmentLifecycleCallback
Expand Down Expand Up @@ -36,7 +37,7 @@ class FragmentLifecycleTest {
contract = TestLifecycleKey()
initialContract = contract
},
contracts = {
fragmentStore = FragmentFlowStore.init {
val featureFactory = object : FeatureFactory<Unit, TestLifecycleKey> {
override fun initialize(dependencies: Unit, key: TestLifecycleKey): Feature {
return Feature(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.reactivex.rxjava3.disposables.Disposable
* navigation destination [com.instacart.formula.fragment.FragmentKey] to its state
* management stream.
*
* @param contracts Fragment state management defined for this [Activity].
* @param fragmentStore Fragment state management defined for this [Activity].
* @param streams This provides ability to configure arbitrary RxJava streams that survive
* configuration changes. Check [com.instacart.formula.android.StreamConfigurator] for utility methods.
* @param configureActivity This is invoked as part of [com.instacart.formula.FormulaAndroid.onPreCreate]. You can
Expand All @@ -18,7 +18,7 @@ import io.reactivex.rxjava3.disposables.Disposable
* @param onFragmentLifecycleEvent This is callback for when a fragment is added or removed.
*/
class ActivityStore<Activity : FragmentActivity>(
val contracts: FragmentFlowStore,
val fragmentStore: FragmentFlowStore,
val streams: (StreamConfigurator<Activity>.() -> Disposable)? = null,
val configureActivity: ((Activity) -> Unit)? = null,
val onRenderFragmentState: ((Activity, FragmentFlowState) -> Unit)? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,58 +79,21 @@ abstract class ActivityStoreContext<out Activity : FragmentActivity> {
* @param onFragmentLifecycleEvent This is called after each [FragmentLifecycleEvent].
* @param streams This provides ability to configure arbitrary RxJava streams that survive
* configuration changes. Check [StreamConfigurator] for utility methods.
* @param contracts [FragmentFlowStore] used to provide state management for individual screens.
* @param fragmentStore [FragmentFlowStore] used to provide state management for individual screens.
*/
fun <ActivityT : FragmentActivity> store(
configureActivity: (ActivityT.() -> Unit)? = null,
onRenderFragmentState: ((ActivityT, FragmentFlowState) -> Unit)? = null,
onFragmentLifecycleEvent: ((FragmentLifecycleEvent) -> Unit)? = null,
streams: (StreamConfigurator<ActivityT>.() -> Disposable)? = null,
contracts: FragmentFlowStore
fragmentStore: FragmentFlowStore = FragmentFlowStore.EMPTY,
): ActivityStore<ActivityT> {
return ActivityStore(
contracts = contracts,
fragmentStore = fragmentStore,
configureActivity = configureActivity,
onFragmentLifecycleEvent = onFragmentLifecycleEvent,
onRenderFragmentState = onRenderFragmentState,
streams = streams
)
}

/**
* Creates an [ActivityStore].
*
* @param configureActivity This is called when activity is created before view inflation. You can use this to
* configure / inject the activity.
* @param onRenderFragmentState This is called after [FragmentFlowState] is applied to UI.
* @param onFragmentLifecycleEvent This is called after each [FragmentLifecycleEvent].
* @param streams This provides ability to configure arbitrary RxJava streams that survive
* configuration changes. Check [StreamConfigurator] for utility methods.
* @param contracts Builder method that configures [FragmentFlowStore] used to provide state management for individual screens.
*/
inline fun <ActivityT : FragmentActivity> store(
noinline configureActivity: (ActivityT.() -> Unit)? = null,
noinline onRenderFragmentState: ((ActivityT, FragmentFlowState) -> Unit)? = null,
noinline onFragmentLifecycleEvent: ((FragmentLifecycleEvent) -> Unit)? = null,
noinline streams: (StreamConfigurator<ActivityT>.() -> Disposable)? = null,
crossinline contracts: FragmentStoreBuilder<Unit>.() -> Unit = {}
): ActivityStore<ActivityT> {
return store(
configureActivity = configureActivity,
onRenderFragmentState = onRenderFragmentState,
onFragmentLifecycleEvent = onFragmentLifecycleEvent,
streams = streams,
contracts = contracts(Unit, contracts)
)
}

/**
* Convenience method to to create a [FragmentFlowStore] with a [Component] instance.
*/
inline fun <Component> contracts(
rootComponent: Component,
crossinline contracts: FragmentStoreBuilder<Component>.() -> Unit
): FragmentFlowStore {
return FragmentFlowStore.init(rootComponent, contracts)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.reactivex.rxjava3.core.Observable
* and the [stateObservable] observable.
*
* To define a feature, we need to create a [FeatureFactory] for a specific [FragmentKey] type
* and [bind][FragmentStoreBuilder.bind] it to the [FragmentFlowStore].
* and [bind][FeaturesBuilder.bind] it to the [FragmentFlowStore].
*
* Take a look at [FeatureFactory] for more information.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ package com.instacart.formula.android
* }
* ```
*
* Once we define a [FeatureFactory], we need to [bind][FragmentStoreBuilder.bind] it to a
* Once we define a [FeatureFactory], we need to [bind][FeaturesBuilder.bind] it to a
* [FragmentFlowStore]. The fragment flow store will call [initialize] the first time
* [FormulaFragment] with a new [Key] is attached. It will subscribe to the state management
* and persist it across configuration changes.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
package com.instacart.formula.android

import com.instacart.formula.android.internal.FeatureBinding
import com.instacart.formula.android.internal.Features
import com.instacart.formula.android.internal.MappedFeatureFactory
import java.lang.IllegalStateException
import kotlin.reflect.KClass

/**
* A class used by [FragmentFlowStore] to register [fragment keys][FragmentKey] and their
* feature factories.
* Helps to build a [Features] list that binds various fragment keys to their respective
* feature factories. Each feature factory has a dependency type that needs to either match
* [Dependencies] type defined here or map this root dependency type to the custom type.
*/
class FragmentStoreBuilder<Component> {
class FeaturesBuilder<Dependencies> {
companion object {
@PublishedApi
internal inline fun <Component> build(
init: FragmentStoreBuilder<Component>.() -> Unit
): List<FeatureBinding<Component, *>> {
return FragmentStoreBuilder<Component>().apply(init).build()
inline fun <Dependencies> build(
init: FeaturesBuilder<Dependencies>.() -> Unit
): Features<Dependencies> {
return FeaturesBuilder<Dependencies>().apply(init).build()
}
}

private val types = mutableSetOf<Class<*>>()
private val bindings: MutableList<FeatureBinding<Component, *>> = mutableListOf()
private val bindings: MutableList<FeatureBinding<Dependencies, *>> = mutableListOf()

/**
* Binds a [feature factory][FeatureFactory] for a specific [key][type].
Expand All @@ -30,7 +31,7 @@ class FragmentStoreBuilder<Component> {
*/
fun <Key : FragmentKey> bind(
type : KClass<Key>,
featureFactory: FeatureFactory<Component, Key>,
featureFactory: FeatureFactory<Dependencies, Key>,
) = apply {
val binding = FeatureBinding(type.java, featureFactory)
bind(type.java, binding)
Expand All @@ -42,7 +43,7 @@ class FragmentStoreBuilder<Component> {
* @param featureFactory Feature factory that provides state observable and view rendering logic.
*/
inline fun <reified Key: FragmentKey> bind(
featureFactory: FeatureFactory<Component, Key>
featureFactory: FeatureFactory<Dependencies, Key>
) = apply {
bind(Key::class, featureFactory)
}
Expand All @@ -51,11 +52,11 @@ class FragmentStoreBuilder<Component> {
* A convenience inline function that binds a feature factory for a specific [key][Key].
*
* @param featureFactory Feature factory that provides state observable and view rendering logic.
* @param toDependencies Maps [Component] to feature factory [dependencies][Dependencies].
* @param toDependencies Maps [Dependencies] to feature factory [dependencies][CustomDependencyType].
*/
inline fun <Dependencies, reified Key: FragmentKey> bind(
featureFactory: FeatureFactory<Dependencies, Key>,
noinline toDependencies: (Component) -> Dependencies
inline fun <CustomDependencyType, reified Key: FragmentKey> bind(
featureFactory: FeatureFactory<CustomDependencyType, Key>,
noinline toDependencies: (Dependencies) -> CustomDependencyType
) = apply {
val mapped = MappedFeatureFactory(
delegate = featureFactory,
Expand All @@ -65,11 +66,11 @@ class FragmentStoreBuilder<Component> {
}

@PublishedApi
internal fun build(): List<FeatureBinding<Component, *>> {
return bindings
internal fun build(): Features<Dependencies> {
return Features(bindings)
}

private fun bind(type: Class<*>, binding: FeatureBinding<Component, *>) = apply {
private fun bind(type: Class<*>, binding: FeatureBinding<Dependencies, *>) = apply {
if (types.contains(type)) {
throw IllegalStateException("Binding for $type already exists")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.instacart.formula.android

import com.instacart.formula.RuntimeConfig
import com.instacart.formula.android.events.FragmentLifecycleEvent
import com.instacart.formula.android.internal.Features
import com.instacart.formula.android.internal.FragmentFlowStoreFormula
import com.instacart.formula.android.utils.MainThreadDispatcher
import com.instacart.formula.rxjava3.toObservable
Expand All @@ -14,18 +15,27 @@ class FragmentFlowStore @PublishedApi internal constructor(
private val formula: FragmentFlowStoreFormula<*>,
) {
companion object {
val EMPTY = init { }

inline fun init(
crossinline init: FragmentStoreBuilder<Unit>.() -> Unit
crossinline init: FeaturesBuilder<Unit>.() -> Unit
): FragmentFlowStore {
return init(Unit, init)
}

inline fun <Component> init(
rootComponent: Component,
crossinline contracts: FragmentStoreBuilder<Component>.() -> Unit
crossinline init: FeaturesBuilder<Component>.() -> Unit
): FragmentFlowStore {
val features = FragmentStoreBuilder.build(init)
return init(rootComponent, features)
}

fun <Component> init(
component: Component,
features: Features<Component>
): FragmentFlowStore {
val bindings = FragmentStoreBuilder.build(contracts)
val formula = FragmentFlowStoreFormula(rootComponent, bindings)
val formula = FragmentFlowStoreFormula(component, features.bindings)
return FragmentFlowStore(formula)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ internal class ActivityManager<Activity : FragmentActivity>(
activity = activity,
fragmentEnvironment = environment,
onLifecycleEvent = {
store.contracts.onLifecycleEffect(it)
store.fragmentStore.onLifecycleEffect(it)
store.onFragmentLifecycleEvent?.invoke(it)
},
onLifecycleState = delegate::updateFragmentLifecycleState,
onFragmentViewStateChanged = store.contracts::onVisibilityChanged
onFragmentViewStateChanged = store.fragmentStore::onVisibilityChanged
)
}

Expand Down Expand Up @@ -108,7 +108,7 @@ internal class ActivityManager<Activity : FragmentActivity>(

private fun subscribeToFragmentStateChanges(): Disposable {
return store
.contracts
.fragmentStore
.state(environment)
.subscribe(delegate.fragmentFlowStateRelay::accept)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.instacart.formula.android.FragmentKey
/**
* Defines how a specific key should be bound to its [FeatureFactory]
*/
class FeatureBinding<in Component, Key : FragmentKey>(
class FeatureBinding<in Dependencies, Key : FragmentKey>(
val type: Class<Key>,
val feature: FeatureFactory<Component, Key>,
val feature: FeatureFactory<Dependencies, Key>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.instacart.formula.android.internal

class Features<in Dependencies>(
val bindings: List<FeatureBinding<Dependencies, *>>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ActivityStoreFactoryTest {
environment = FragmentEnvironment(),
activities = {
activity(FakeActivity::class) {
store { }
store()
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import android.util.Log
import com.instacart.formula.FormulaAndroid
import com.instacart.formula.android.FragmentEnvironment
import com.instacart.formula.android.FragmentFlowStore

class StopwatchApp : Application() {

Expand All @@ -20,7 +21,7 @@ class StopwatchApp : Application() {
activities = {
activity<StopwatchActivity> {
store(
contracts = contracts(Unit) {
fragmentStore = FragmentFlowStore.init(Unit) {
bind(StopwatchFeatureFactory())
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.util.Log
import com.examples.todoapp.tasks.TaskListFeatureFactory
import com.instacart.formula.FormulaAndroid
import com.instacart.formula.android.FragmentEnvironment
import com.instacart.formula.android.FragmentFlowStore

class TodoApp : Application() {

Expand All @@ -23,7 +24,7 @@ class TodoApp : Application() {
val component = TodoAppComponent(this)

store(
contracts = contracts(component) {
fragmentStore = FragmentFlowStore.init(component) {
bind(TaskListFeatureFactory())
}
)
Expand Down

0 comments on commit db3b8f0

Please sign in to comment.