From 49b6596874a3f7a83c6166a247560a93adda3df9 Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Wed, 4 Sep 2024 11:54:24 -0400 Subject: [PATCH] Rename FragmentStoreBuilder to FeaturesBuilder. --- CHANGELOG.md | 3 +- .../formula/FragmentAndroidEventTest.kt | 3 +- .../formula/FragmentFlowRenderViewTest.kt | 3 +- .../formula/FragmentLifecycleStateTest.kt | 3 +- .../formula/FragmentLifecycleTest.kt | 3 +- .../formula/android/ActivityStore.kt | 4 +- .../formula/android/ActivityStoreContext.kt | 43 +--------- .../formula/android/FeaturesBuilder.kt | 80 +++++++++++++++++++ .../formula/android/FragmentFlowStore.kt | 16 +++- .../formula/android/FragmentStoreBuilder.kt | 79 +----------------- .../android/internal/ActivityManager.kt | 6 +- .../android/internal/FeatureBinding.kt | 4 +- .../formula/android/internal/Features.kt | 5 ++ .../android/ActivityStoreFactoryTest.kt | 2 +- .../formula/compose/stopwatch/StopwatchApp.kt | 3 +- .../main/java/com/examples/todoapp/TodoApp.kt | 3 +- 16 files changed, 125 insertions(+), 135 deletions(-) create mode 100644 formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt create mode 100644 formula-android/src/main/java/com/instacart/formula/android/internal/Features.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 73461e4f0..9d22c9cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentAndroidEventTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentAndroidEventTest.kt index 517aaa05d..2f487fa84 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentAndroidEventTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentAndroidEventTest.kt @@ -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 @@ -29,7 +30,7 @@ class FragmentAndroidEventTest { configureActivity = { initialContract = TestLifecycleKey() }, - contracts = { + fragmentStore = FragmentFlowStore.init { val featureFactory = object : FeatureFactory { override fun initialize(dependencies: Unit, key: TestLifecycleKey): Feature { return Feature( diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentFlowRenderViewTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentFlowRenderViewTest.kt index 333c8a4b1..6520c38c7 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentFlowRenderViewTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentFlowRenderViewTest.kt @@ -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 @@ -49,7 +50,7 @@ class FragmentFlowRenderViewTest { updateThreads.add(Thread.currentThread()) }, - contracts = { + fragmentStore = FragmentFlowStore.init { bind(TestFeatureFactory { stateChanges(it) }) bind(TestFeatureFactory { stateChanges(it) }) } diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt index ac3401457..1125d2620 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt @@ -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 @@ -36,7 +37,7 @@ class FragmentLifecycleStateTest { configureActivity = { initialContract = TestKey() }, - contracts = { + fragmentStore = FragmentFlowStore.init { bind(featureFactory(this@activity)) bind(featureFactory(this@activity)) } diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt index a08d461a1..7939fe90b 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt @@ -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 @@ -36,7 +37,7 @@ class FragmentLifecycleTest { contract = TestLifecycleKey() initialContract = contract }, - contracts = { + fragmentStore = FragmentFlowStore.init { val featureFactory = object : FeatureFactory { override fun initialize(dependencies: Unit, key: TestLifecycleKey): Feature { return Feature( diff --git a/formula-android/src/main/java/com/instacart/formula/android/ActivityStore.kt b/formula-android/src/main/java/com/instacart/formula/android/ActivityStore.kt index 0e21c0347..ee44fb7e2 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/ActivityStore.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/ActivityStore.kt @@ -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 @@ -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( - val contracts: FragmentFlowStore, + val fragmentStore: FragmentFlowStore, val streams: (StreamConfigurator.() -> Disposable)? = null, val configureActivity: ((Activity) -> Unit)? = null, val onRenderFragmentState: ((Activity, FragmentFlowState) -> Unit)? = null, diff --git a/formula-android/src/main/java/com/instacart/formula/android/ActivityStoreContext.kt b/formula-android/src/main/java/com/instacart/formula/android/ActivityStoreContext.kt index 70709c7f3..b3e5b66c5 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/ActivityStoreContext.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/ActivityStoreContext.kt @@ -79,58 +79,21 @@ abstract class ActivityStoreContext { * @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 store( configureActivity: (ActivityT.() -> Unit)? = null, onRenderFragmentState: ((ActivityT, FragmentFlowState) -> Unit)? = null, onFragmentLifecycleEvent: ((FragmentLifecycleEvent) -> Unit)? = null, streams: (StreamConfigurator.() -> Disposable)? = null, - contracts: FragmentFlowStore + fragmentStore: FragmentFlowStore = FragmentFlowStore.EMPTY, ): ActivityStore { 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 store( - noinline configureActivity: (ActivityT.() -> Unit)? = null, - noinline onRenderFragmentState: ((ActivityT, FragmentFlowState) -> Unit)? = null, - noinline onFragmentLifecycleEvent: ((FragmentLifecycleEvent) -> Unit)? = null, - noinline streams: (StreamConfigurator.() -> Disposable)? = null, - crossinline contracts: FragmentStoreBuilder.() -> Unit = {} - ): ActivityStore { - 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 contracts( - rootComponent: Component, - crossinline contracts: FragmentStoreBuilder.() -> Unit - ): FragmentFlowStore { - return FragmentFlowStore.init(rootComponent, contracts) - } } diff --git a/formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt b/formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt new file mode 100644 index 000000000..a5765af92 --- /dev/null +++ b/formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt @@ -0,0 +1,80 @@ +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 + +/** + * 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 FeaturesBuilder { + companion object { + inline fun build( + init: FeaturesBuilder.() -> Unit + ): Features { + return FeaturesBuilder().apply(init).build() + } + } + + private val types = mutableSetOf>() + private val bindings: MutableList> = mutableListOf() + + /** + * Binds a [feature factory][FeatureFactory] for a specific [key][type]. + * + * @param type The class which describes the [key][Key]. + * @param featureFactory Feature factory that provides state observable and view rendering logic. + */ + fun bind( + type : KClass, + featureFactory: FeatureFactory, + ) = apply { + val binding = FeatureBinding(type.java, featureFactory) + bind(type.java, binding) + } + + /** + * Binds a feature factory for a [Key]. + * + * @param featureFactory Feature factory that provides state observable and view rendering logic. + */ + inline fun bind( + featureFactory: FeatureFactory + ) = apply { + bind(Key::class, featureFactory) + } + + /** + * 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 [Dependencies] to feature factory [dependencies][CustomDependencyType]. + */ + inline fun bind( + featureFactory: FeatureFactory, + noinline toDependencies: (Dependencies) -> CustomDependencyType + ) = apply { + val mapped = MappedFeatureFactory( + delegate = featureFactory, + toDependencies = toDependencies, + ) + bind(Key::class, mapped) + } + + @PublishedApi + internal fun build(): Features { + return Features(bindings) + } + + private fun bind(type: Class<*>, binding: FeatureBinding) = apply { + if (types.contains(type)) { + throw IllegalStateException("Binding for $type already exists") + } + types += type + bindings += binding + } +} diff --git a/formula-android/src/main/java/com/instacart/formula/android/FragmentFlowStore.kt b/formula-android/src/main/java/com/instacart/formula/android/FragmentFlowStore.kt index 7302e0437..8fda58019 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FragmentFlowStore.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FragmentFlowStore.kt @@ -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 @@ -14,6 +15,8 @@ class FragmentFlowStore @PublishedApi internal constructor( private val formula: FragmentFlowStoreFormula<*>, ) { companion object { + val EMPTY = init { } + inline fun init( crossinline init: FragmentStoreBuilder.() -> Unit ): FragmentFlowStore { @@ -22,10 +25,17 @@ class FragmentFlowStore @PublishedApi internal constructor( inline fun init( rootComponent: Component, - crossinline contracts: FragmentStoreBuilder.() -> Unit + crossinline init: FragmentStoreBuilder.() -> Unit + ): FragmentFlowStore { + val features = FragmentStoreBuilder.build(init) + return init(rootComponent, features) + } + + fun init( + component: Component, + features: Features ): FragmentFlowStore { - val bindings = FragmentStoreBuilder.build(contracts) - val formula = FragmentFlowStoreFormula(rootComponent, bindings) + val formula = FragmentFlowStoreFormula(component, features.bindings) return FragmentFlowStore(formula) } } diff --git a/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt b/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt index f3e906233..98e254a7d 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt @@ -1,79 +1,4 @@ package com.instacart.formula.android -import com.instacart.formula.android.internal.FeatureBinding -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. - */ -class FragmentStoreBuilder { - companion object { - @PublishedApi - internal inline fun build( - init: FragmentStoreBuilder.() -> Unit - ): List> { - return FragmentStoreBuilder().apply(init).build() - } - } - - private val types = mutableSetOf>() - private val bindings: MutableList> = mutableListOf() - - /** - * Binds a [feature factory][FeatureFactory] for a specific [key][type]. - * - * @param type The class which describes the [key][Key]. - * @param featureFactory Feature factory that provides state observable and view rendering logic. - */ - fun bind( - type : KClass, - featureFactory: FeatureFactory, - ) = apply { - val binding = FeatureBinding(type.java, featureFactory) - bind(type.java, binding) - } - - /** - * Binds a feature factory for a [Key]. - * - * @param featureFactory Feature factory that provides state observable and view rendering logic. - */ - inline fun bind( - featureFactory: FeatureFactory - ) = apply { - bind(Key::class, featureFactory) - } - - /** - * 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]. - */ - inline fun bind( - featureFactory: FeatureFactory, - noinline toDependencies: (Component) -> Dependencies - ) = apply { - val mapped = MappedFeatureFactory( - delegate = featureFactory, - toDependencies = toDependencies, - ) - bind(Key::class, mapped) - } - - @PublishedApi - internal fun build(): List> { - return bindings - } - - private fun bind(type: Class<*>, binding: FeatureBinding) = apply { - if (types.contains(type)) { - throw IllegalStateException("Binding for $type already exists") - } - types += type - bindings += binding - } -} +@Deprecated("Use FeaturesBuilder directly") +typealias FragmentStoreBuilder = FeaturesBuilder diff --git a/formula-android/src/main/java/com/instacart/formula/android/internal/ActivityManager.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/ActivityManager.kt index 9089aeb24..51bdc818a 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/internal/ActivityManager.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/internal/ActivityManager.kt @@ -43,11 +43,11 @@ internal class ActivityManager( 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 ) } @@ -108,7 +108,7 @@ internal class ActivityManager( private fun subscribeToFragmentStateChanges(): Disposable { return store - .contracts + .fragmentStore .state(environment) .subscribe(delegate.fragmentFlowStateRelay::accept) } diff --git a/formula-android/src/main/java/com/instacart/formula/android/internal/FeatureBinding.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/FeatureBinding.kt index fd4e7732e..2e88540e9 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/internal/FeatureBinding.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/internal/FeatureBinding.kt @@ -6,7 +6,7 @@ import com.instacart.formula.android.FragmentKey /** * Defines how a specific key should be bound to its [FeatureFactory] */ -class FeatureBinding( +class FeatureBinding( val type: Class, - val feature: FeatureFactory, + val feature: FeatureFactory, ) diff --git a/formula-android/src/main/java/com/instacart/formula/android/internal/Features.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/Features.kt new file mode 100644 index 000000000..4cedf8d3c --- /dev/null +++ b/formula-android/src/main/java/com/instacart/formula/android/internal/Features.kt @@ -0,0 +1,5 @@ +package com.instacart.formula.android.internal + +class Features( + val bindings: List>, +) diff --git a/formula-android/src/test/java/com/instacart/formula/android/ActivityStoreFactoryTest.kt b/formula-android/src/test/java/com/instacart/formula/android/ActivityStoreFactoryTest.kt index 62e7c26b7..df7b7f086 100644 --- a/formula-android/src/test/java/com/instacart/formula/android/ActivityStoreFactoryTest.kt +++ b/formula-android/src/test/java/com/instacart/formula/android/ActivityStoreFactoryTest.kt @@ -18,7 +18,7 @@ class ActivityStoreFactoryTest { environment = FragmentEnvironment(), activities = { activity(FakeActivity::class) { - store { } + store() } } ) diff --git a/samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchApp.kt b/samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchApp.kt index 67f744391..c263decbe 100644 --- a/samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchApp.kt +++ b/samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchApp.kt @@ -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() { @@ -20,7 +21,7 @@ class StopwatchApp : Application() { activities = { activity { store( - contracts = contracts(Unit) { + fragmentStore = FragmentFlowStore.init(Unit) { bind(StopwatchFeatureFactory()) } ) diff --git a/samples/todoapp/src/main/java/com/examples/todoapp/TodoApp.kt b/samples/todoapp/src/main/java/com/examples/todoapp/TodoApp.kt index 8429b16e3..ae4a785f9 100644 --- a/samples/todoapp/src/main/java/com/examples/todoapp/TodoApp.kt +++ b/samples/todoapp/src/main/java/com/examples/todoapp/TodoApp.kt @@ -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() { @@ -23,7 +24,7 @@ class TodoApp : Application() { val component = TodoAppComponent(this) store( - contracts = contracts(component) { + fragmentStore = FragmentFlowStore.init(component) { bind(TaskListFeatureFactory()) } )