1515 */
1616package com.squareup.workflow.ui.compose.internal
1717
18- import android.content.Context
19- import android.view.ViewGroup.LayoutParams.MATCH_PARENT
20- import android.widget.FrameLayout
18+ import android.view.View
19+ import android.view.ViewGroup
2120import androidx.compose.Composable
21+ import androidx.compose.compositionReference
22+ import androidx.compose.onPreCommit
23+ import androidx.compose.remember
24+ import androidx.ui.core.AndroidOwner
25+ import androidx.ui.core.ContextAmbient
2226import androidx.ui.core.Modifier
27+ import androidx.ui.core.OwnerAmbient
28+ import androidx.ui.core.Ref
2329import androidx.ui.foundation.Box
30+ import androidx.ui.viewinterop.AndroidView
2431import com.squareup.workflow.ui.ViewEnvironment
2532import com.squareup.workflow.ui.ViewFactory
26- import com.squareup.workflow.ui.WorkflowViewStub
33+ import com.squareup.workflow.ui.canShowRendering
2734import com.squareup.workflow.ui.compose.ComposeViewFactory
28- import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Update
35+ import com.squareup.workflow.ui.showRendering
2936
3037/* *
3138 * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to
@@ -49,49 +56,73 @@ import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Updat
4956) {
5057 val viewFactory = this
5158 Box (modifier = modifier) {
52- // Fast path: If the child binding is also a Composable, we don't need to go through the legacy
53- // view system and can just invoke the binding's composable function directly.
59+ // " Fast" path: If the child binding is also a Composable, we don't need to go through the
60+ // legacy view system and can just invoke the binding's composable function directly.
5461 if (viewFactory is ComposeViewFactory ) {
5562 viewFactory.showRenderingWrappedWithRoot(rendering, viewEnvironment)
56- } else {
57- // Plumb the current composition "context" through the ViewEnvironment so any nested
58- // composable factories get access to any ambients currently in effect.
59- // See setOrContinueContent().
60- val newEnvironment = viewEnvironment.withParentComposition()
61-
62- // IntelliJ currently complains very loudly about this function call, but it actually
63- // compiles. The IDE tooling isn't currently able to recognize that the Compose compiler
64- // accepts this code.
65- ComposableViewStubWrapper (update = Update (rendering, newEnvironment))
63+ return @Box
6664 }
65+
66+ // "Slow" path: Create a legacy Android View to show the rendering, like WorkflowViewStub.
67+ ViewFactoryAndroidView (viewFactory, rendering, viewEnvironment)
6768 }
6869}
6970
7071/* *
71- * Wraps a [WorkflowViewStub] with an API that is more Compose-friendly.
72+ * This is effectively the logic of [com.squareup.workflow.ui.WorkflowViewStub], but translated
73+ * into Compose idioms. This approach has a few advantages:
74+ *
75+ * - Avoids extra custom views required to host `WorkflowViewStub` inside a Composition. Its trick
76+ * of replacing itself in its parent doesn't play nice with Compose.
77+ * - Allows us to pass the correct parent view for inflation (the root of the composition).
78+ * - Avoids `WorkflowViewStub` having to do its own lookup to find the correct [ViewFactory], since
79+ * we already have the correct one.
7280 *
73- * In particular, Compose will only generate `Emittable`s for views with a single constructor
74- * that takes a [Context].
81+ * Like `WorkflowViewStub`, this function uses the [viewFactory] to create and memoize a [View] to
82+ * display the [rendering], keeps it updated with the latest [rendering] and [viewEnvironment], and
83+ * adds it to the composition.
7584 *
76- * See [this slack message](https://kotlinlang.slack.com/archives/CJLTWPH7S/p1576264533012000?thread_ts=1576262311.008800&cid=CJLTWPH7S).
85+ * This function also passes a [ParentComposition] down through the [ViewEnvironment] so that if the
86+ * child view further nests any `ComposableViewFactory`s, they will be correctly subcomposed.
7787 */
78- private class ComposableViewStubWrapper (context : Context ) : FrameLayout(context) {
88+ @Composable private fun <R : Any > ViewFactoryAndroidView (
89+ viewFactory : ViewFactory <R >,
90+ rendering : R ,
91+ viewEnvironment : ViewEnvironment
92+ ) {
93+ val childView = remember { Ref <View >() }
7994
80- data class Update (
81- val rendering : Any ,
82- val viewEnvironment : ViewEnvironment
83- )
95+ // Plumb the current composition through the ViewEnvironment so any nested composable factories
96+ // get access to any ambients currently in effect. See setOrSubcomposeContent().
97+ val parentComposition = remember { ParentComposition () }
98+ parentComposition.reference = compositionReference()
99+ val wrappedEnvironment = remember(viewEnvironment) {
100+ viewEnvironment + (ParentComposition to parentComposition)
101+ }
84102
85- private val viewStub = WorkflowViewStub (context)
103+ // A view factory can decide to recreate its view at any time. This also covers the case where
104+ // the value of the viewFactory argument has changed, including to one with a different type.
105+ if (childView.value?.canShowRendering(rendering) != true ) {
106+ // If we don't pass the parent Android View, the child will have the wrong LayoutParams.
107+ // OwnerAmbient is deprecated, but the only way to get the root view currently. I've filed
108+ // a feature request to expose this as first-class API, see
109+ // https://issuetracker.google.com/issues/156875705.
110+ @Suppress(" DEPRECATION" )
111+ val parentView = (OwnerAmbient .current as ? AndroidOwner )?.view as ? ViewGroup
86112
87- init {
88- addView(viewStub)
113+ childView.value = viewFactory.buildView(
114+ initialRendering = rendering,
115+ initialViewEnvironment = wrappedEnvironment,
116+ contextForNewView = ContextAmbient .current,
117+ container = parentView
118+ )
89119 }
90120
91- // Compose turns this into a parameter when you invoke this class as a Composable.
92- fun setUpdate (update : Update ) {
93- viewStub.update(update.rendering, update.viewEnvironment)
121+ // Invoke the ViewFactory's update logic whenever the view, the rendering, or the ViewEnvironment
122+ // change.
123+ onPreCommit(childView.value, rendering, wrappedEnvironment) {
124+ childView.value!! .showRendering(rendering, wrappedEnvironment)
94125 }
95126
96- override fun getLayoutParams (): LayoutParams = LayoutParams ( MATCH_PARENT , MATCH_PARENT )
127+ AndroidView (childView.value !! )
97128}
0 commit comments