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 e34c49bb..82917b55 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 @@ -1,8 +1,6 @@ package com.instacart.formula.android -import android.content.Context import android.os.Bundle -import android.os.SystemClock import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -30,25 +28,38 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { requireArguments().getParcelable(ARG_CONTRACT)!! } + private val formulaFragmentId: FragmentId by lazy { + getFormulaFragmentId() + } + + private val environment: FragmentEnvironment + get() = FormulaFragmentDelegate.fragmentEnvironment() + + private val fragmentDelegate: FragmentEnvironment.FragmentDelegate + get() = environment.fragmentDelegate + + private var calledNewInstance = false + private var featureView: FeatureView? = null private var output: Any? = 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) - if (initializedAtMillis == null) { - initializedAtMillis = SystemClock.uptimeMillis() + override fun setArguments(args: Bundle?) { + super.setArguments(args) + + /** + * To ensure that we have both fragment key and formula instance id, we need + * to wait for arguments to be set. + */ + if (!calledNewInstance) { + calledNewInstance = true + fragmentDelegate.onNewInstance(formulaFragmentId) } } 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 @@ -102,8 +113,6 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { } override fun onDestroyView() { - initializedAtMillis = null - lifecycleCallback?.onDestroyView() super.onDestroyView() featureView = null @@ -130,22 +139,8 @@ class FormulaFragment : Fragment(), BaseFormulaFragment { val output = output ?: return val view = featureView ?: return - val fragmentId = getFormulaFragmentId() - val environment = FormulaFragmentDelegate.fragmentEnvironment() - val fragmentDelegate = environment.fragmentDelegate - try { - fragmentDelegate.setOutput(fragmentId, output, view.setOutput) - - if (firstRender) { - val end = SystemClock.uptimeMillis() - - firstRender = false - fragmentDelegate.onFirstModelRendered( - fragmentId = fragmentId, - durationInMillis = end - (initializedAtMillis ?: SystemClock.uptimeMillis()), - ) - } + fragmentDelegate.setOutput(formulaFragmentId, output, view.setOutput) } catch (exception: Exception) { environment.onScreenError(key, exception) } diff --git a/formula-android/src/main/java/com/instacart/formula/android/FragmentEnvironment.kt b/formula-android/src/main/java/com/instacart/formula/android/FragmentEnvironment.kt index a44f468b..a69eac33 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/FragmentEnvironment.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/FragmentEnvironment.kt @@ -14,6 +14,13 @@ data class FragmentEnvironment( */ open class FragmentDelegate { + /** + * Called when new instance of [FormulaFragment] is created. + */ + open fun onNewInstance( + fragmentId: FragmentId + ) = Unit + /** * Instantiates the feature. */ @@ -44,11 +51,5 @@ data class FragmentEnvironment( open fun setOutput(fragmentId: FragmentId, output: Any, applyOutputToView: (Any) -> Unit) { applyOutputToView(output) } - - /** - * Called after first render model is rendered. The [durationInMillis] starts - * when formula fragment is initialized and ends after first render model is applied. - */ - open fun onFirstModelRendered(fragmentId: FragmentId, durationInMillis: Long) = Unit } } diff --git a/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentFlowRenderView.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentFlowRenderView.kt index 1a29f17b..724cd1a6 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentFlowRenderView.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentFlowRenderView.kt @@ -95,8 +95,6 @@ internal class FragmentFlowRenderView( override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) { super.onFragmentAttached(fm, f, context) - initializeFragmentInstanceIdIfNeeded(f) - if (FragmentLifecycle.shouldTrack(f)) { onLifecycleEvent(FragmentLifecycle.createAddedEvent(f)) } else { @@ -141,7 +139,6 @@ internal class FragmentFlowRenderView( } fun viewFactory(fragment: FormulaFragment): ViewFactory { - initializeFragmentInstanceIdIfNeeded(fragment) return FormulaFragmentViewFactory( environment = fragmentEnvironment, fragmentId = fragment.getFormulaFragmentId(), @@ -162,23 +159,4 @@ internal class FragmentFlowRenderView( } } } - - /** - * Creates a unique identifier the first time fragment is attached that - * is persisted across configuration changes. - */ - private fun initializeFragmentInstanceIdIfNeeded(f: Fragment) { - if (f is BaseFormulaFragment<*>) { - val arguments = f.arguments ?: run { - Bundle().apply { - f.arguments = this - } - } - val id = arguments.getString(FormulaFragment.ARG_FORMULA_ID, "") - if (id.isNullOrBlank()) { - val initializedId = UUID.randomUUID().toString() - arguments.putString(FormulaFragment.ARG_FORMULA_ID, initializedId) - } - } - } } diff --git a/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentLifecycle.kt b/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentLifecycle.kt index 2a26f6d2..dd574f1b 100644 --- a/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentLifecycle.kt +++ b/formula-android/src/main/java/com/instacart/formula/android/internal/FragmentLifecycle.kt @@ -1,5 +1,6 @@ package com.instacart.formula.android.internal +import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentInspector import androidx.fragment.app.FragmentManager @@ -8,6 +9,7 @@ import com.instacart.formula.android.FragmentKey import com.instacart.formula.android.BaseFormulaFragment import com.instacart.formula.android.FormulaFragment import com.instacart.formula.android.events.FragmentLifecycleEvent +import java.util.UUID /** * Provides utility method [lifecycleEvents] to track what fragments are added and removed from the backstack. @@ -37,8 +39,28 @@ private fun Fragment.getFragmentKey(): FragmentKey { return fragment?.getFragmentKey() ?: EmptyFragmentKey(tag.orEmpty()) } +/** + * Gets a persisted across configuration changes fragment identifier or initializes + * one if it doesn't exist. + */ private fun Fragment.getFragmentInstanceId(): String { - return arguments?.getString(FormulaFragment.ARG_FORMULA_ID) ?: "" + return if (this is BaseFormulaFragment<*>) { + val arguments = arguments ?: run { + Bundle().apply { + arguments = this + } + } + val id = arguments.getString(FormulaFragment.ARG_FORMULA_ID, "") + if (id.isNullOrBlank()) { + val initializedId = UUID.randomUUID().toString() + arguments.putString(FormulaFragment.ARG_FORMULA_ID, initializedId) + initializedId + } else { + id + } + } else { + "" + } } internal fun Fragment.getFormulaFragmentId(): FragmentId {