@@ -25,6 +25,9 @@ import android.widget.FrameLayout
2525import androidx.compose.Composable
2626import androidx.compose.Recomposer
2727import androidx.compose.StructurallyEqual
28+ import androidx.compose.frames.commit
29+ import androidx.compose.frames.inFrame
30+ import androidx.compose.frames.open
2831import androidx.compose.mutableStateOf
2932import androidx.ui.core.setContent
3033import com.squareup.workflow.ui.ViewEnvironment
@@ -78,26 +81,50 @@ internal class ComposeViewFactory<RenderingT : Any>(
7881 // Composable function, so we need to use ViewGroup.setContent.
7982 val composeContainer = FrameLayout (contextForNewView)
8083
81- // This model will be used to feed state updates into the composition.
84+ // Create a single MutableState to feed state updates into the composition.
85+ // We could also have two separate MutableStates, but using a Pair both makes it clear and
86+ // enforces that both values are always updated together.
8287 val renderState = mutableStateOf<Pair <RenderingT , ViewEnvironment >? > (
88+ // This will be updated immediately by bindShowRendering below.
8389 value = null ,
8490 areEquivalent = StructurallyEqual
8591 )
8692
87- // Entry point to the composition.
93+ // If this is the first ever compose call in this process, we need to manually open a frame
94+ // before updating the MutableState.
95+ ensureComposeFrame {
96+ // Update the state whenever a new rendering is emitted.
97+ composeContainer.bindShowRendering(
98+ initialRendering,
99+ initialViewEnvironment
100+ ) { rendering, environment ->
101+ // This will be called synchronously before bindShowRendering returns.
102+ renderState.value = Pair (rendering, environment)
103+ }
104+ }
105+
106+ // Entry point to the world of Compose.
88107 composeContainer.setContent(Recomposer .current()) {
89- // Don't compose anything until we have the first value (which should happen in the initial
90- // frame).
91- val (rendering, environment) = renderState.value ? : return @setContent
108+ val (rendering, environment) = renderState.value!!
92109 showRendering(rendering, environment)
93110 }
94111
95- composeContainer.bindShowRendering(
96- initialRendering,
97- initialViewEnvironment
98- ) { rendering, environment ->
99- renderState.value = Pair (rendering, environment)
100- }
101112 return composeContainer
102113 }
114+
115+ /* *
116+ * Currently, if we try setting a Model's value before any other Compose code has started running
117+ * (e.g. by calling setContent), there will not be a frame open so the model set will throw. This
118+ * function detects that situation and creates a temporary frame if necessary. To run [block] in.
119+ * This should be removed once Compose gets support for global frames.
120+ */
121+ private fun ensureComposeFrame (block : () -> Unit ) {
122+ if (! inFrame) {
123+ val frame = open(readOnly = false )
124+ block()
125+ commit(frame)
126+ } else {
127+ block()
128+ }
129+ }
103130}
0 commit comments