-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BREAKING: Replaces ComposeScreenViewFactory
with ScreenComposableFactory
#1146
Conversation
c22a09a
to
9f5afc2
Compare
ComposeScreenViewFactory
with ScreenComposableFactory
ComposeScreenViewFactory
with ScreenComposableFactory
9f5afc2
to
7ceee3d
Compare
dea82c8
to
ffb5279
Compare
b060cc9
to
ab4ae71
Compare
This is looking really solid. I don't quite have our internal test suite passing yet, but so far everything I've found has been newly exposed bugs in our workflow integration, especially the hellish duct tape supporiting our remaining pre-workflow code. I'm pretty optimistic about shipping this month. |
workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/ViewRegistry.kt
Show resolved
Hide resolved
workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt
Outdated
Show resolved
Hide resolved
22d5182
to
95d0e69
Compare
f1b5ba3
to
ec95ab1
Compare
529c40b
to
4f3163e
Compare
…ing type Paves the way for parallel Classic and Compose implementations of wrappers like `NamedScreen`, to fix #546. Ironically, breaks Compose support in the process, but that is fixed by the next commit.
01585e9
to
94e9ff8
Compare
} | ||
|
||
onView(withText("two")).check(matches(isDisplayed())) | ||
wrapperText.value = "OWT" | ||
onView(withText("OWT")).check(matches(isDisplayed())) | ||
} | ||
|
||
@Test fun namedScreenStaysInTheSameComposeView() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ta da!
* otherwise it wraps the factory in one that manages a classic Android view. | ||
*/ | ||
@OptIn(WorkflowUiExperimentalApi::class) | ||
private fun <ScreenT : Screen> ScreenViewFactory<ScreenT>.asComposeViewFactory() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This became ScreenComposableFactoryFinder
13968d1
to
2ca9cad
Compare
|
||
The above two concepts coordinate, and when a Compose-based factory is delegating to a child rendering that is also bound to a composable factory, we can skip the detour out into the Android view world and simply call the child composable directly from the parent. | ||
> You'll note that there is no `OverlayComposableFactory` family of interfaces. So far, all of our window management is strictly via classic Android `Dialog` calls. There is nothing stopping us (or you) from adding Compose-based `Overlay` support in the future as a replacement, but we're definitely not making any promises on that front. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ekeitho 👀
|
||
/** | ||
* Like [ScreenComposableFactory.Preview], but for non-Compose [ScreenViewFactory] instances. | ||
* Yes, you can preview classic [View][android.view.View] code this way. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa, neat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
@@ -1,3 +0,0 @@ | |||
# Module compose-tooling | |||
|
|||
TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good riddance ;)
|
||
/** | ||
* Like [ScreenComposableFactory.Preview], but for non-Compose [ScreenViewFactory] instances. | ||
* Yes, you can preview classic [View][android.view.View] code this way. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
...-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt
Outdated
Show resolved
Hide resolved
...-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt
Outdated
Show resolved
Hide resolved
...compose/src/main/java/com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupport.kt
Outdated
Show resolved
Hide resolved
200a131
to
dd63fdf
Compare
public fun Screen.Preview( | ||
modifier: Modifier = Modifier, | ||
placeholderModifier: Modifier = Modifier, | ||
viewEnvironmentUpdater: ((ViewEnvironment) -> ViewEnvironment)? = null | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😮
…actory` This commit takes advantage of the new `ViewRegistry.Key` (which allows renderings to be bound to multiple UI factories) to fix a long standing problem where wrapper screens -- things like `NamedScreen` and `EnvironmentScreen` -- used in a Compose context would cause needless calls to `@Composeable fun AndroidView()` and `ComposeView()`. For example, consider this rendering: ``` BodyAndOverlaysScreen( body = SomeComposeScreen( EnvironmentScreen( SomeOtherComposeScreen ) ) ) ``` Before this change, that would create a View hierarchy something like this: ``` BodyAndOverlaysContainer : FrameLayout { mChildren[0] = ComposeView { // compose land SomeComposeScreen.Content { AndroidView { ComposeView { // nested compose land SomeOtherComposeScreen.Content() ``` Now it will look this way: ``` BodyAndOverlaysContainer : FrameLayout { mChildren[0] = ComposeView { // compose land SomeComposeScreen.Content { SomeOtherComposeScreen.Content() ``` `ScreenComposableFactory` replaces `ComposeScreenViewFactory`, and `ComposeScreen` no longer extends `AndroidScreen`. Compose support is now a first class citizen, instead of a hack bolted on to View support. Unfortunately, `ViewEnvironment.withComposeInteropSupport()` (see below) now must be called near the root of a Workflow app to enable the seamless Compose > Classic > Compose handling that used to be built in to `WorkflowRendering` and `ComposeScreenViewFactory`. This means that call is required for Compose support for built in rendering types like `BodyAndOverlaysScreen` and `BackStackScreen`, which so far are backed only by classic View implementations. Other introductions, changes: - `Screen.toComposableFactory()`, used by `WorkflowRendering()` in the same way that `WorkflowViewStub` uses `Screen.toViewFactory()` - `ScreenComposableFactoryFinder`, a `ViewEnvironment`-based strategy object used by `Screen.toComposableFactory()` the same way that `Screen.toViewFactory()` uses `ScreenViewFactoryFinder`. The default implementation provides Compose bindings for `NamedScreen` and `EnvironmentScreen`, fixing #546. - `ScreenViewFactoryFinder.getViewFactoryForRendering()` can now return `null`. A `requireViewFactoryForRendering()` extension is introduced for use when `null` is not acceptable. - `ViewEnvironment.withComposeInteropSupport()`, which wraps the found `ScreenComposableFactoryFinder` and `ScreenViewFactoryFinder` with implementations that allow Compose contexts to handle renderings bound only to `ScreenViewFactory`, and classic contexts to handle renderings bound only to `ScreenComposableFactory`. Replaces the logic that used to be in the private `ScreenViewFactory.asComposeViewFactory()` extension in `WorkflowRendering()`. - `Screen.Preview()` is introduced. The existing `Preview()` extension functions were tied to `ScreenViewFactory`, making them much less useful. It is still the case that previews work for non-Compose UI code just fine. Which is pretty cool, really. Fixes #546
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt
Show resolved
Hide resolved
* | ||
*/ | ||
@WorkflowUiExperimentalApi | ||
public interface ScreenComposableFactory<in ScreenT : Screen> : ViewRegistry.Entry<ScreenT> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outside of scope of this, but out of curiosity if we were wanting to add a compose overlay factory.
would that entail creating a new ComposeOverlayDialogFactory
which implements ScreenComposableFactory
& then adding to the checks in ScreenComposableFactoryFinder searching for ComposeOverlayDialogFactory
?
or would that entail creating a new factory like this one, with it's relative factory finder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require:
-
A Compose binding (
ScreenComposableFactory
implementation) forBodyAndOverlaysScreen
-- an alternative to the existingBodyAndOverlaysContainer
-
A new
OverlayComposableFactory
/OverlayComposableFactoryFinder
pairing
That's assuming you weren't going to use any non-Compose dialog code at all.
If you were going to try to make it coexist with the DialogOverlayFactory
you would also need to enhance the logic in withComposeInteropSupport()
. But that would only be feasible if your Compose version was still going to create Dialog
instances, so that Compose-managed ones could interleave with legacy instances. Even then, you would somehow have to make DialogCollator
et al agnostic between Dialog
intances created via Compose
v. those created the other way. No idea if that's practical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah exactly not going to use any non-compose dialog code at all - thanks for explaining!
As soon as this merges (still trying to get our #*&$(& benchmarks to run), we should follow up with a new Compose-based BackStackScreen treatment that fixes #669. |
Benchmarks look good. No real improvement in the instrumented portion of our real world app, but that's to be expected since our Compose code is still almost entirely at the leaf level. But the reassurance that the latency around those leaves didn't climb is meaningful. |
@@ -13,6 +13,10 @@ android { | |||
composeOptions { | |||
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() | |||
} | |||
defaultConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, didn't mean to merge this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not accident, required by a crummy old AVD version. But this ripples to break other things (how did I get a green PR?). Need to consider spreading the blast radius or else upgrading the AVDs.
@RBusarow @steve-the-edwards, what's your kneejerk?
Please be sure to review
workflow-ui/compose/README.md
, which GitHub helpfully hides by default because it's big.There are two commits in this PR, and it'd be wise to review them in sequence:
ViewRegistry.Key
allows multiple factory types per rendering typePaves the way for parallel Classic and Compose implementations of wrappers like
NamedScreen
, to fix #546. Ironically, breaks Compose support in the process, but that is fixed by the next commit.Replaces
ComposeScreenViewFactory
withScreenComposableFactory
This commit takes advantage of the new
ViewRegistry.Key
(which allows renderings to be bound to multiple UI factories) to fix a long standing problem where wrapper screens -- things likeNamedScreen
andEnvironmentScreen
-- used in a Compose context would cause needless calls to@Composeable fun AndroidView()
andComposeView()
.For example, consider this rendering:
Before this change, that would create a View hierarchy something like this:
Now it will look this way:
ScreenComposableFactory
replacesComposeScreenViewFactory
, andComposeScreen
no longer extendsAndroidScreen
. Compose support is now a first class citizen, instead of a hack bolted on to View support.Unfortunately,
ViewEnvironment.withComposeInteropSupport()
(see below) now must be called near the root of a Workflow app to enable the seamless Compose > Classic > Compose handling that used to be built in toWorkflowRendering
andComposeScreenViewFactory
. This means that call is required for Compose support for built in rendering types likeBodyAndOverlaysScreen
andBackStackScreen
, which so far are backed only by classic View implementations.Other introductions, changes:
Screen.toComposableFactory()
, used byWorkflowRendering()
in the same way thatWorkflowViewStub
usesScreen.toViewFactory()
ScreenComposableFactoryFinder
, aViewEnvironment
-based strategy object used byScreen.toComposableFactory()
the same way thatScreen.toViewFactory()
usesScreenViewFactoryFinder
. The default implementation provides Compose bindings forNamedScreen
andEnvironmentScreen
, fixing Wrapper renderings introduce an extra AndroidView unnecessarily when the wrapped rendering's factory is a ComposeScreenViewFactory. #546.ScreenViewFactoryFinder.getViewFactoryForRendering()
can now returnnull
. ArequireViewFactoryForRendering()
extension is introduced for use whennull
is not acceptable.ViewEnvironment.withComposeInteropSupport()
, which wraps the foundScreenComposableFactoryFinder
andScreenViewFactoryFinder
with implementations that allow Compose contexts to handle renderings bound only toScreenViewFactory
, and classic contexts to handle renderings bound only toScreenComposableFactory
. Replaces the logic that used to be in the privateScreenViewFactory.asComposeViewFactory()
extension inWorkflowRendering()
.Screen.Preview()
is introduced. The existingPreview()
extension functions were tied toScreenViewFactory
, making them much less useful. It is still the case that previews work for non-Compose UI code just fine. Which is pretty cool, really.Fixes #546