Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename FragmentStoreBuilder to FeaturesBuilder. #381

Merged
merged 2 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider a rename from FragmentFlowStore to maybe just FragmentStore as well? When I see Flow, I always just think Coroutine Flow so it can be a little jarring to realize that its not that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, will do in a follow up.

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 = FeaturesBuilder.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
Loading