diff --git a/formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt b/formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt index e32a4573..6c5b7678 100644 --- a/formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt +++ b/formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt @@ -1,6 +1,5 @@ package com.instacart.formula.android.compose -import android.os.SystemClock import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.runtime.Composable @@ -8,38 +7,23 @@ import androidx.compose.runtime.rxjava3.subscribeAsState import androidx.compose.ui.platform.ComposeView import com.instacart.formula.android.FeatureView import com.instacart.formula.android.ViewFactory +import com.jakewharton.rxrelay3.BehaviorRelay abstract class ComposeViewFactory : ViewFactory { override fun create(inflater: LayoutInflater, container: ViewGroup?): FeatureView { val view = ComposeView(inflater.context) - var firstRender = true + val outputRelay = BehaviorRelay.create() + view.setContent { + val model = outputRelay.subscribeAsState(null).value + if (model != null) { + Content(model) + } + } return FeatureView( view = view, - bind = { state -> - view.setContent { - val model = state.observable.subscribeAsState(null).value - if (model != null) { - val start = SystemClock.uptimeMillis() - Content(model) - val end = SystemClock.uptimeMillis() - state.environment.eventListener?.onRendered( - fragmentId = state.fragmentId, - durationInMillis = end - start, - ) - - if (firstRender) { - firstRender = false - state.environment.eventListener?.onFirstModelRendered( - fragmentId = state.fragmentId, - durationInMillis = end - state.initializedAtMillis, - ) - } - } - } - null - } + setOutput = outputRelay::accept, ) } diff --git a/formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt b/formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt index f4b24a53..55d4ad97 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt @@ -13,18 +13,11 @@ import io.reactivex.rxjava3.core.Observable * [FeatureView]. * * @param view The root Android view. - * @param bind A bind function connects state observable to the view rendering. + * @param setOutput A function called to apply [RenderModel] to the view. * @param lifecycleCallbacks Optional lifecycle callbacks if you need to know the Fragment state. */ class FeatureView( val view: View, - val bind: (State) -> Cancelable?, + val setOutput: (RenderModel) -> Unit, val lifecycleCallbacks: FragmentLifecycleCallback? = null, -) { - class State( - val initializedAtMillis: Long, - val fragmentId: FragmentId, - val environment: FragmentEnvironment, - val observable: Observable, - ) -} \ No newline at end of file +) diff --git a/formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt b/formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt index 1ea8f23a..9129cd64 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt @@ -7,10 +7,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import com.instacart.formula.Cancelable import com.instacart.formula.android.internal.FormulaFragmentDelegate import com.instacart.formula.android.internal.getFormulaFragmentId -import com.jakewharton.rxrelay3.BehaviorRelay +import java.lang.Exception class FormulaFragment : Fragment(), BaseFormulaFragment { companion object { @@ -31,13 +30,14 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { requireArguments().getParcelable(ARG_CONTRACT)!! } - private var initializedAtMillis: Long? = SystemClock.uptimeMillis() - private var featureView: FeatureView? = null - private val stateRelay: BehaviorRelay = BehaviorRelay.create() - private var cancelable: Cancelable? = null + private var output: Any? = null - private var lifecycleCallback: FragmentLifecycleCallback? = null + private var initializedAtMillis: Long? = SystemClock.uptimeMillis() + private var firstRender = true + + private val lifecycleCallback: FragmentLifecycleCallback? + get() = featureView?.lifecycleCallbacks override fun onAttach(context: Context) { super.onAttach(context) @@ -47,6 +47,8 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + firstRender = true + val viewFactory = FormulaFragmentDelegate.viewFactory(this) ?: run { // No view factory, no view return null @@ -59,17 +61,9 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - featureView?.let { value -> - val state = FeatureView.State( - initializedAtMillis = initializedAtMillis ?: SystemClock.uptimeMillis(), - fragmentId = getFormulaFragmentId(), - environment = FormulaFragmentDelegate.fragmentEnvironment(), - observable = stateRelay, - ) - cancelable = value.bind(state) - this.lifecycleCallback = value.lifecycleCallbacks - lifecycleCallback?.onViewCreated(view, savedInstanceState) - } + tryToSetState() + + lifecycleCallback?.onViewCreated(view, savedInstanceState) } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -110,21 +104,18 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { override fun onDestroyView() { initializedAtMillis = null - cancelable?.cancel() - cancelable = null - lifecycleCallback?.onDestroyView() - lifecycleCallback = null super.onDestroyView() featureView = null } override fun setState(state: Any) { - stateRelay.accept(state) + output = state + tryToSetState() } override fun currentState(): Any? { - return stateRelay.value + return output } override fun getFragmentKey(): FragmentKey { @@ -134,4 +125,34 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { override fun toString(): String { return "${key.tag} -> $key" } + + private fun tryToSetState() { + val output = output ?: return + val view = featureView ?: return + + val fragmentId = getFormulaFragmentId() + val environment = FormulaFragmentDelegate.fragmentEnvironment() + + try { + val start = SystemClock.uptimeMillis() + view.setOutput(output) + val end = SystemClock.uptimeMillis() + + + environment.eventListener?.onRendered( + fragmentId = fragmentId, + durationInMillis = end - start, + ) + + if (firstRender) { + firstRender = false + environment.eventListener?.onFirstModelRendered( + fragmentId = fragmentId, + durationInMillis = end - (initializedAtMillis ?: SystemClock.uptimeMillis()), + ) + } + } catch (exception: Exception) { + environment.onScreenError(key, exception) + } + } } diff --git a/formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt b/formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt index 2123b939..aafb9b1a 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt @@ -3,7 +3,6 @@ package com.instacart.formula.android import android.view.View import com.instacart.formula.RenderView import com.instacart.formula.Renderer -import com.instacart.formula.android.views.FeatureViewBindFunction /** * View instance contains an initialized Android [view] and provides factory @@ -37,7 +36,7 @@ abstract class ViewInstance { ): FeatureView { return FeatureView( view = view, - bind = FeatureViewBindFunction(renderer), + setOutput = renderer, lifecycleCallbacks = lifecycleCallbacks ) } diff --git a/formula-android/src/main/java/com/instacart/formula/android/views/FeatureViewBindFunction.kt b/formula-android/src/main/java/com/instacart/formula/android/views/FeatureViewBindFunction.kt deleted file mode 100644 index 56f083e5..00000000 --- a/formula-android/src/main/java/com/instacart/formula/android/views/FeatureViewBindFunction.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.instacart.formula.android.views - -import android.os.SystemClock -import com.instacart.formula.Cancelable -import com.instacart.formula.Renderer -import com.instacart.formula.android.FeatureView -import java.lang.Exception - -/** - * Binds [FeatureView.State] to a [render] function. - */ -internal class FeatureViewBindFunction( - private val render: Renderer -) : (FeatureView.State) -> Cancelable? { - override fun invoke(state: FeatureView.State): Cancelable { - val environment = state.environment - var firstRender = true - val disposable = state.observable.subscribe { - try { - val start = SystemClock.uptimeMillis() - render(it) - val end = SystemClock.uptimeMillis() - environment.eventListener?.onRendered( - fragmentId = state.fragmentId, - durationInMillis = end - start, - ) - if (firstRender) { - firstRender = false - environment.eventListener?.onFirstModelRendered( - fragmentId = state.fragmentId, - durationInMillis = end - state.initializedAtMillis, - ) - } - } catch (exception: Exception) { - environment.onScreenError(state.fragmentId.key, exception) - } - } - return Cancelable { - disposable.dispose() - } - } -} \ No newline at end of file