Skip to content

Commit d352bb1

Browse files
authored
Merge pull request #1121 from square/ray/tighten-up-backstack-types
Makes `BackStackScreen<*>` practical
2 parents ad05052 + b1c9a12 commit d352bb1

File tree

26 files changed

+256
-171
lines changed

26 files changed

+256
-171
lines changed

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ typealias IsLoading = Boolean
1717

1818
@OptIn(WorkflowUiExperimentalApi::class)
1919
class MaybeLoadingGatekeeperWorkflow<T : Any>(
20-
private val childWithLoading: Workflow<T, Any, OverviewDetailScreen>,
20+
private val childWithLoading: Workflow<T, Any, OverviewDetailScreen<*>>,
2121
private val childProps: T,
2222
private val isLoading: Flow<Boolean>
2323
) : StatefulWorkflow<Unit, IsLoading, Unit, MayBeLoadingScreen>() {

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import com.squareup.workflow1.WorkflowAction
3232
import com.squareup.workflow1.WorkflowAction.Companion.noAction
3333
import com.squareup.workflow1.action
3434
import com.squareup.workflow1.runningWorker
35-
import com.squareup.workflow1.ui.Screen
3635
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
3736
import com.squareup.workflow1.ui.container.BackStackScreen
3837
import com.squareup.workflow1.ui.container.toBackStackScreen
@@ -60,7 +59,7 @@ import kotlinx.coroutines.flow.flow
6059
class PerformancePoemWorkflow(
6160
private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF,
6261
private val isLoading: MutableStateFlow<Boolean>,
63-
) : PoemWorkflow, StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen>() {
62+
) : PoemWorkflow, StatefulWorkflow<Poem, State, ClosePoem, OverviewDetailScreen<*>>() {
6463

6564
sealed class State {
6665
val isLoading: Boolean = false
@@ -100,7 +99,7 @@ class PerformancePoemWorkflow(
10099
renderProps: Poem,
101100
renderState: State,
102101
context: RenderContext
103-
): OverviewDetailScreen {
102+
): OverviewDetailScreen<*> {
104103
if (simulatedPerfConfig.simultaneousActions > 0) {
105104
repeat(simulatedPerfConfig.simultaneousActions) { index ->
106105
context.runningWorker(
@@ -222,7 +221,7 @@ class PerformancePoemWorkflow(
222221
}
223222

224223
val stackedStanzas = visibleStanza?.let {
225-
(previousStanzas + visibleStanza).toBackStackScreen<Screen>()
224+
(previousStanzas + visibleStanza).toBackStackScreen()
226225
}
227226

228227
val stanzaListOverview =

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.squareup.benchmarks.performance.complex.poetry.instrumentation.Tracea
1212
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker
1313
import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen
1414
import com.squareup.sample.container.overviewdetail.OverviewDetailScreen
15+
import com.squareup.sample.container.overviewdetail.plus
1516
import com.squareup.sample.poetry.ConfigAndPoems
1617
import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED
1718
import com.squareup.sample.poetry.PoemListWorkflow
@@ -51,7 +52,7 @@ class PerformancePoemsBrowserWorkflow(
5152
private val isLoading: MutableStateFlow<Boolean>,
5253
) :
5354
PoemsBrowserWorkflow,
54-
StatefulWorkflow<ConfigAndPoems, State, Unit, OverviewDetailScreen>() {
55+
StatefulWorkflow<ConfigAndPoems, State, Unit, OverviewDetailScreen<*>>() {
5556

5657
sealed class State {
5758
object Recurse : State()
@@ -88,7 +89,7 @@ class PerformancePoemsBrowserWorkflow(
8889
renderProps: ConfigAndPoems,
8990
renderState: State,
9091
context: RenderContext
91-
): OverviewDetailScreen {
92+
): OverviewDetailScreen<*> {
9293
when (renderState) {
9394
is Recurse -> {
9495
val recursiveChild = PerformancePoemsBrowserWorkflow(
@@ -188,13 +189,13 @@ class PerformancePoemsBrowserWorkflow(
188189
}
189190
}
190191
}
191-
var poems = OverviewDetailScreen(
192+
var poems: OverviewDetailScreen<*> = OverviewDetailScreen(
192193
overviewRendering = BackStackScreen(
193194
poemListRendering.copy(selection = renderState.payload)
194195
)
195196
)
196197
if (renderState.payload != NO_POEM_SELECTED) {
197-
val poem: OverviewDetailScreen = context.renderChild(
198+
val poem = context.renderChild(
198199
poemWorkflow,
199200
renderProps.second[renderState.payload]
200201
) { clearSelection }
@@ -209,7 +210,7 @@ class PerformancePoemsBrowserWorkflow(
209210
poemListRendering.copy(selection = renderState.poemIndex)
210211
)
211212
)
212-
val poem: OverviewDetailScreen = context.renderChild(
213+
val poem = context.renderChild(
213214
poemWorkflow,
214215
renderProps.second[renderState.poemIndex]
215216
) { clearSelection }

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import com.squareup.workflow1.ui.container.FullScreenModal
88

99
@OptIn(WorkflowUiExperimentalApi::class)
1010
typealias MayBeLoadingScreen =
11-
BodyAndOverlaysScreen<ScrimScreen<OverviewDetailScreen>, FullScreenModal<LoaderSpinner>>
11+
BodyAndOverlaysScreen<ScrimScreen<OverviewDetailScreen<*>>, FullScreenModal<LoaderSpinner>>
1212

1313
@OptIn(WorkflowUiExperimentalApi::class)
1414
fun MayBeLoadingScreen(
15-
baseScreen: OverviewDetailScreen,
15+
baseScreen: OverviewDetailScreen<*>,
1616
loaders: List<LoaderSpinner> = emptyList()
1717
): MayBeLoadingScreen {
1818
return BodyAndOverlaysScreen(

samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.squareup.workflow1.ui.ViewEnvironment
1414
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1515
import com.squareup.workflow1.ui.WorkflowViewStub
1616
import com.squareup.workflow1.ui.container.BackStackScreen
17+
import com.squareup.workflow1.ui.container.plus
1718

1819
/**
1920
* Displays [OverviewDetailScreen] renderings in either split pane or single pane
@@ -25,7 +26,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen
2526
* with [OverviewDetailScreen.overviewRendering] as the base of the stack.
2627
*/
2728
@OptIn(WorkflowUiExperimentalApi::class)
28-
class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScreen> {
29+
class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScreen<*>> {
2930

3031
private val overviewStub: WorkflowViewStub? = view.findViewById(R.id.overview_stub)
3132
private val detailStub: WorkflowViewStub? = view.findViewById(R.id.detail_stub)
@@ -42,7 +43,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScree
4243
}
4344

4445
override fun showRendering(
45-
rendering: OverviewDetailScreen,
46+
rendering: OverviewDetailScreen<*>,
4647
environment: ViewEnvironment
4748
) {
4849
if (singleStub == null) {
@@ -53,7 +54,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScree
5354
}
5455

5556
private fun renderSplitView(
56-
rendering: OverviewDetailScreen,
57+
rendering: OverviewDetailScreen<*>,
5758
viewEnvironment: ViewEnvironment
5859
) {
5960
if (rendering.detailRendering == null && rendering.selectDefault != null) {
@@ -81,7 +82,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScree
8182
}
8283

8384
private fun renderSingleView(
84-
rendering: OverviewDetailScreen,
85+
rendering: OverviewDetailScreen<*>,
8586
viewEnvironment: ViewEnvironment,
8687
stub: WorkflowViewStub
8788
) {
@@ -92,7 +93,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner<OverviewDetailScree
9293
stub.show(combined, viewEnvironment + Single)
9394
}
9495

95-
companion object : ScreenViewFactory<OverviewDetailScreen> by ScreenViewFactory.fromLayout(
96+
companion object : ScreenViewFactory<OverviewDetailScreen<*>> by ScreenViewFactory.fromLayout(
9697
layoutId = R.layout.overview_detail,
9798
constructor = ::OverviewDetailContainer
9899
)

samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.content.Context
44
import android.graphics.Rect
55
import androidx.appcompat.app.AppCompatDialog
66
import com.squareup.sample.container.R
7-
import com.squareup.workflow1.ui.Screen
87
import com.squareup.workflow1.ui.ViewEnvironment
98
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
109
import com.squareup.workflow1.ui.container.OverlayDialogFactory
@@ -17,22 +16,22 @@ import kotlin.reflect.KClass
1716
* Android support for [PanelOverlay].
1817
*/
1918
@OptIn(WorkflowUiExperimentalApi::class)
20-
internal object PanelOverlayDialogFactory : OverlayDialogFactory<PanelOverlay<Screen>> {
21-
override val type: KClass<in PanelOverlay<Screen>> = PanelOverlay::class
19+
internal object PanelOverlayDialogFactory : OverlayDialogFactory<PanelOverlay<*>> {
20+
override val type: KClass<in PanelOverlay<*>> = PanelOverlay::class
2221

2322
override fun buildDialog(
24-
initialRendering: PanelOverlay<Screen>,
23+
initialRendering: PanelOverlay<*>,
2524
initialEnvironment: ViewEnvironment,
2625
context: Context
27-
): OverlayDialogHolder<PanelOverlay<Screen>> {
26+
): OverlayDialogHolder<PanelOverlay<*>> {
2827
val dialog = AppCompatDialog(context, R.style.PanelDialog)
2928

3029
val realHolder = dialog.asDialogHolderWithContent(initialRendering, initialEnvironment)
3130

3231
// We replace the default onUpdateBounds function with one that gives the
3332
// panel a square shape on tablets. See OverlayDialogFactory for more details
3433
// on the bounds mechanism.
35-
return object : OverlayDialogHolder<PanelOverlay<Screen>> by realHolder {
34+
return object : OverlayDialogHolder<PanelOverlay<*>> by realHolder {
3635
override val onUpdateBounds: ((Rect) -> Unit) = { bounds ->
3736
val refinedBounds: Rect = if (!dialog.context.isTablet) {
3837
// On a phone, fill the bounds entirely.

samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.squareup.sample.container.overviewdetail
33
import com.squareup.workflow1.ui.Screen
44
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
55
import com.squareup.workflow1.ui.container.BackStackScreen
6+
import com.squareup.workflow1.ui.container.plus
67

78
/**
89
* Rendering type for overview / detail containers, with [BackStackScreen] in both roles.
@@ -16,51 +17,33 @@ import com.squareup.workflow1.ui.container.BackStackScreen
1617
* or a null [selectDefault]. **[selectDefault] cannot be a no-op.**
1718
*/
1819
@OptIn(WorkflowUiExperimentalApi::class)
19-
class OverviewDetailScreen private constructor(
20-
val overviewRendering: BackStackScreen<Screen>,
21-
val detailRendering: BackStackScreen<Screen>? = null,
20+
class OverviewDetailScreen<out T : Screen> private constructor(
21+
val overviewRendering: BackStackScreen<T>,
22+
val detailRendering: BackStackScreen<T>? = null,
2223
val selectDefault: (() -> Unit)? = null
2324
) : Screen {
2425
constructor(
25-
overviewRendering: BackStackScreen<Screen>,
26-
detailRendering: BackStackScreen<Screen>
26+
overviewRendering: BackStackScreen<T>,
27+
detailRendering: BackStackScreen<T>
2728
) : this(overviewRendering, detailRendering, null)
2829

2930
/**
3031
* @param selectDefault optional function that a split view container may call to request
3132
* that a selection be made to fill a null [detailRendering].
3233
*/
3334
constructor(
34-
overviewRendering: BackStackScreen<Screen>,
35+
overviewRendering: BackStackScreen<T>,
3536
selectDefault: (() -> Unit)? = null
3637
) : this(overviewRendering, null, selectDefault)
3738

38-
operator fun component1(): BackStackScreen<Screen> = overviewRendering
39-
operator fun component2(): BackStackScreen<Screen>? = detailRendering
40-
41-
/**
42-
* Returns a new [OverviewDetailScreen] appending the [overviewRendering] and
43-
* [detailRendering] of [other] to those of the receiver. If the new screen's
44-
* [detailRendering] is `null`, it will have the [selectDefault] function of [other].
45-
*/
46-
operator fun plus(other: OverviewDetailScreen): OverviewDetailScreen {
47-
val newOverview = overviewRendering + other.overviewRendering
48-
val newDetail = detailRendering
49-
?.let { it + other.detailRendering }
50-
?: other.detailRendering
51-
52-
return if (newDetail == null) {
53-
OverviewDetailScreen(newOverview, other.selectDefault)
54-
} else {
55-
OverviewDetailScreen(newOverview, newDetail)
56-
}
57-
}
39+
operator fun component1(): BackStackScreen<T> = overviewRendering
40+
operator fun component2(): BackStackScreen<T>? = detailRendering
5841

5942
override fun equals(other: Any?): Boolean {
6043
if (this === other) return true
6144
if (javaClass != other?.javaClass) return false
6245

63-
other as OverviewDetailScreen
46+
other as OverviewDetailScreen<*>
6447

6548
return overviewRendering == other.overviewRendering &&
6649
detailRendering == other.detailRendering &&
@@ -80,3 +63,26 @@ class OverviewDetailScreen private constructor(
8063
"selectDefault=$selectDefault)"
8164
}
8265
}
66+
67+
/**
68+
* Returns a new [OverviewDetailScreen] appending the
69+
* [overviewRendering][OverviewDetailScreen.overviewRendering] and
70+
* [detailRendering][OverviewDetailScreen.detailRendering] of [other] to those of the receiver.
71+
* If the new screen's `detailRendering` is `null`, it will have the
72+
* [selectDefault][OverviewDetailScreen.selectDefault] function of [other].
73+
*/
74+
@OptIn(WorkflowUiExperimentalApi::class)
75+
operator fun <T : Screen> OverviewDetailScreen<T>.plus(
76+
other: OverviewDetailScreen<T>
77+
): OverviewDetailScreen<T> {
78+
val newOverview = overviewRendering + other.overviewRendering
79+
val newDetail = detailRendering
80+
?.let { it + other.detailRendering }
81+
?: other.detailRendering
82+
83+
return if (newDetail == null) {
84+
OverviewDetailScreen(newOverview, other.selectDefault)
85+
} else {
86+
OverviewDetailScreen(newOverview, newDetail)
87+
}
88+
}

samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.squareup.workflow1.ui.container.ModalOverlay
66
import com.squareup.workflow1.ui.container.ScreenOverlay
77

88
@OptIn(WorkflowUiExperimentalApi::class)
9-
class PanelOverlay<C : Screen>(
9+
class PanelOverlay<out C : Screen>(
1010
override val content: C
1111
) : ScreenOverlay<C>, ModalOverlay {
1212
override fun <D : Screen> map(transform: (C) -> D): PanelOverlay<D> =

0 commit comments

Comments
 (0)