Skip to content

Commit

Permalink
[Fragment] Replace bind with setOutput function. (#319)
Browse files Browse the repository at this point in the history
* [Fragment] Replace bind with setOutput function.

* Remove extra line.
  • Loading branch information
Laimiux authored Dec 12, 2023
1 parent 70f0258 commit 85664df
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,45 +1,29 @@
package com.instacart.formula.android.compose

import android.os.SystemClock
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.runtime.Composable
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<RenderModel> : ViewFactory<RenderModel> {

override fun create(inflater: LayoutInflater, container: ViewGroup?): FeatureView<RenderModel> {
val view = ComposeView(inflater.context)
var firstRender = true
val outputRelay = BehaviorRelay.create<RenderModel>()
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,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<RenderModel>(
val view: View,
val bind: (State<RenderModel>) -> Cancelable?,
val setOutput: (RenderModel) -> Unit,
val lifecycleCallbacks: FragmentLifecycleCallback? = null,
) {
class State<RenderModel>(
val initializedAtMillis: Long,
val fragmentId: FragmentId,
val environment: FragmentEnvironment,
val observable: Observable<RenderModel>,
)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<Any> {
companion object {
Expand All @@ -31,13 +30,14 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {
requireArguments().getParcelable<FragmentKey>(ARG_CONTRACT)!!
}

private var initializedAtMillis: Long? = SystemClock.uptimeMillis()

private var featureView: FeatureView<Any>? = null
private val stateRelay: BehaviorRelay<Any> = 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)
Expand All @@ -47,6 +47,8 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {
}

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
Expand All @@ -59,17 +61,9 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {

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?) {
Expand Down Expand Up @@ -110,21 +104,18 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {
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 {
Expand All @@ -134,4 +125,33 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -37,7 +36,7 @@ abstract class ViewInstance {
): FeatureView<RenderModel> {
return FeatureView(
view = view,
bind = FeatureViewBindFunction(renderer),
setOutput = renderer,
lifecycleCallbacks = lifecycleCallbacks
)
}
Expand Down

This file was deleted.

0 comments on commit 85664df

Please sign in to comment.