diff --git a/CHANGELOG.md b/CHANGELOG.md index 73461e4f..9d22c9cc 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 517aaa05..2f487fa8 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 333c8a4b..6520c38c 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 ac340145..1125d262 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 a08d461a..7939fe90 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 0e21c034..ee44fb7e 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 70709c7f..b3e5b66c 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/Feature.kt b/formula-android/src/main/java/com/instacart/formula/android/Feature.kt index 5d472ad3..d1e29ba5 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/Feature.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/Feature.kt @@ -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. */ diff --git a/formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt b/formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt index d81a4fcc..8219ad51 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt @@ -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. diff --git a/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt b/formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt similarity index 58% rename from formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt rename to formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt index f3e90623..a5765af9 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FragmentStoreBuilder.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FeaturesBuilder.kt @@ -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 { +class FeaturesBuilder { companion object { - @PublishedApi - internal inline fun build( - init: FragmentStoreBuilder.() -> Unit - ): List> { - return FragmentStoreBuilder().apply(init).build() + inline fun build( + init: FeaturesBuilder.() -> Unit + ): Features { + return FeaturesBuilder().apply(init).build() } } private val types = mutableSetOf>() - private val bindings: MutableList> = mutableListOf() + private val bindings: MutableList> = mutableListOf() /** * Binds a [feature factory][FeatureFactory] for a specific [key][type]. @@ -30,7 +31,7 @@ class FragmentStoreBuilder { */ fun bind( type : KClass, - featureFactory: FeatureFactory, + featureFactory: FeatureFactory, ) = apply { val binding = FeatureBinding(type.java, featureFactory) bind(type.java, binding) @@ -42,7 +43,7 @@ class FragmentStoreBuilder { * @param featureFactory Feature factory that provides state observable and view rendering logic. */ inline fun bind( - featureFactory: FeatureFactory + featureFactory: FeatureFactory ) = apply { bind(Key::class, featureFactory) } @@ -51,11 +52,11 @@ class FragmentStoreBuilder { * 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 bind( - featureFactory: FeatureFactory, - noinline toDependencies: (Component) -> Dependencies + inline fun bind( + featureFactory: FeatureFactory, + noinline toDependencies: (Dependencies) -> CustomDependencyType ) = apply { val mapped = MappedFeatureFactory( delegate = featureFactory, @@ -65,11 +66,11 @@ class FragmentStoreBuilder { } @PublishedApi - internal fun build(): List> { - return bindings + internal fun build(): Features { + return Features(bindings) } - private fun bind(type: Class<*>, binding: FeatureBinding) = apply { + private fun bind(type: Class<*>, binding: FeatureBinding) = apply { if (types.contains(type)) { throw IllegalStateException("Binding for $type already exists") } 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 7302e043..4a6de20d 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,18 +15,27 @@ class FragmentFlowStore @PublishedApi internal constructor( private val formula: FragmentFlowStoreFormula<*>, ) { companion object { + val EMPTY = init { } + inline fun init( - crossinline init: FragmentStoreBuilder.() -> Unit + crossinline init: FeaturesBuilder.() -> Unit ): FragmentFlowStore { return init(Unit, init) } inline fun init( rootComponent: Component, - crossinline contracts: FragmentStoreBuilder.() -> Unit + crossinline init: FeaturesBuilder.() -> 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/internal/ActivityManager.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/ActivityManager.kt index 9089aeb2..51bdc818 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 fd4e7732..2e88540e 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 00000000..4cedf8d3 --- /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 62e7c26b..df7b7f08 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 67f74439..c263decb 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 8429b16e..ae4a785f 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()) } )