-
Notifications
You must be signed in to change notification settings - Fork 112
wip: ViewRegistry overhaul, ViewRendering, ModalRendering #202
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
Conversation
| api(Dependencies.Kotlin.Stdlib.jdk6) | ||
|
|
||
| implementation(Dependencies.AndroidX.activity) | ||
| implementation(Dependencies.AndroidX.appcompat) |
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.
Uh-oh. Did we move this out of ui-core to keep @Armaxis happy? Is that still a concern?
I think I can avoid it, but it'll be a bit tedious.
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 longer a concern, with the amount of things we need these day, appcompat is imminent.
Bring it back. Sorry for bothering you remove it in first place :/
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.
Yay! Glad I asked.
| * ) | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| class BespokeViewBuilder<RenderingT : ViewRendering>( |
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.
Not even sure if this is worth providing. Should try just implementing the interface somewhere.
| } | ||
| Quitting -> { | ||
| val dialog = AlertScreen( | ||
| val dialog = AlertModalRendering( |
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 is the punchline. AlertModalRendering is bound in the ViewRegistry to AlertDialogBuilder. We didn't have to make a subclass of ModalContainerView in order to show this particular kind of modal.
| contextForNewView: Context, | ||
| container: ViewGroup? = null | ||
| ): View { | ||
| val builder = initialViewEnvironment[ViewRegistry].getEntryFor(this::class) |
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.
Very tempting to allow the RenderingT itself to implement ViewBuilder
val builder = (this as? ViewBuilder<RenderingT>)
?: initialViewEnvironment[ViewRegistry].getEntryFor(this::class)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.
Actually, better to do it in the other order so that ViewRegistry can override default implementations.
val builder = initialViewEnvironment[ViewRegistry].getEntryFor(this::class)
?: (this as? ViewBuilder<RenderingT>)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.
IIRC the way we talked ourselves out of that was because we didn’t want to encourage screen value types from being coupled to the UI layer. But I think in reality, screen types and view factories are often defined in public-facing, API, android-dependent modules already. And this isn’t requiring that, it merely allows it, which can be super useful for things like the WorkflowEditText wiring, the ComposeRendering PoC, etc. So, I'm on board.
For context on the compose thing, see the comments on square/workflow-kotlin-compose#8.
Builds the simpler, more flexible scheme for modals proposed in #195. Only AlertDialog takes advantage of it so far, and only the `hellobackbutton` and `helloworkflow` samples are ported. closes #195 * We introduce the `ViewRendering` and `ModalRendering` interfaces, similar in sprit to `AnyScreen` in Swift. * `ViewRegistry` changes to be able to hold a collection of more arbitrary entry types, not just `ViewFactory`. * Its `buildView` method is deprecated. * Once we delete that method, we should be able to move `ViewRegistry` over to the non-Android, core UI module. * There are three implementations of `ViewRegistry.Entry` * Our old friend `ViewFactory<T: Any>`, now deprecated. * `ViewBuilder<T: ViewRendering>`, which replaces `ViewFactory` * `DialogBuilder<T: ModalRendering>` * Extension method `ViewRendering.buildView()` replaces `ViewRegistry.buildView()`. We also introduce `ModalRendering.buildDialog()`. * These methods find the appropriate builder in `ViewRegistry`, based on the concrete type of the rendering, just like `ViewRegistry.buildView()` did. * The new ability of `ViewRegistry` to hold arbitrary entries means that we can add as many rendering:view-machinery pairings as we want to. Maybe cleans up Compose integration? * The abstract `ModalContainer` and its subclasses (`AlertContainer`, `ModalViewContainer`, our internal ones) are all deprecated in favor of a single final class: `ModalContainerView`, which is able to display any kind of `ModalRendering` associated with a `DialogBuilder` implementation. * `LayoutRunner<T: Any>` is deprecated, replaced by `ViewRunner<T: ViewRendering>`. That's going to be a tedious migration for existing `LayoutRunner` implementations, but it avoids a huge breaking change — the new mechanism works in parallel with the deprecated one. * `WorkflowViewStub.display(rendering: ViewRendering)` replaces `WorkflowViewStub.update(rendering: Any)`, now deprecated. But the latter is able to delegate to the former so incremental migration is possible.
f520e16 to
7818bf4
Compare
| import kotlinx.android.parcel.Parcelize | ||
|
|
||
| @OptIn(WorkflowUiExperimentalApi::class) | ||
| private typealias Rendering = ModalContainerViewRendering<*, AlertModalRendering> |
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.
I've noticed this 'generic type aliasing' pattern with our Actions and now Rendering/Screens for Workflows. I've always felt like it makes things harder to follow rather than easier because I need to constantly look up what the actual types are. What advantage am I missing with this pattern?
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.
Note that this one is private -- it was strictly a convenience to avoid some copy / paste, and arguably a bad choice for the reaons you mentioned. Our trend has been to use fewer and fewer type aliases, especially public ones.
Some of the lingering ones are leftovers from before we had the more concise action {} lambdas.
| import android.view.ViewGroup | ||
| import kotlin.reflect.KClass | ||
|
|
||
| /** |
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.
Do we need some kind of documentation here just for archaeology?
| * has never been called. | ||
| */ | ||
| @WorkflowUiExperimentalApi | ||
| fun <RenderingT : ModalRendering> Dialog.getDisplaying(): RenderingT? { |
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.
Is it worth an interface with showRenderingTag and displaying so we can abstract some of these duplicated functions into the same implementation?
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.
nvm I see these extension properties are defined below (which of course they are as this is Dialog and View). No clear way to pull out an abstraction as an extension on Dialog and View I guess. 🤔
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.
I guess you do almost already have an extension abstraction in ShowRenderingTag - why can't all these other extensions delegate into functions defined on that class itself. That way we wouldn't have the double maintenance. Granted this may not scale much beyond Dialog and View.
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.
Except for would it if we introduce Compose?
| for ((i, modalRendering) in newScreen.modals.withIndex()) { | ||
| updateDialogs += | ||
| if (i < dialogs.size && compatible(dialogs[i].getDisplaying()!!, modalRendering)) { | ||
| dialogs[i].apply { display(modalRendering, viewEnvironment) } |
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.
AFAIK alert dialogs can't be updated in place. Perhaps AlertModalRendering should implement Compatible?
|
This change seems to require all screen renderings to be completely opinionated about whether they should be a modal or not. Is that true? What happens if a rendering type wants to be usable as either a modal or a non-modal, and so implements both marker interfaces? Do we explicitly allow/forbid that, do we check for it anywhere? As a straw-view, consider |
My original thought was that |
Builds the simpler, more flexible scheme for modals proposed in #195. Only
AlertDialog takes advantage of it so far, and only the
hellobackbuttonandhelloworkflowsamples are ported.closes #195
We introduce the
ViewRenderingandModalRenderinginterfaces,similar in sprit to
AnyScreenin Swift.ViewRegistrychanges to be able to hold a collection of more arbitrary entry types, not justViewFactory.buildViewmethod is deprecated.ViewRegistryover to thenon-Android, core UI module.
There are three implementations of
ViewRegistry.EntryViewFactory<T: Any>, now deprecated.ViewBuilder<T: ViewRendering>, which replacesViewFactoryDialogBuilder<T: ModalRendering>Extension method
ViewRendering.buildView()replacesViewRegistry.buildView(). We also introduceModalRendering.buildDialog().These methods find the appropriate builder in
ViewRegistry, based on the concrete type of the rendering, just likeViewRegistry.buildView()did.The new ability of
ViewRegistryto hold arbitrary entries means that we can add as many rendering:view-machinery pairings as we want to. Maybe cleans up Compose integration? (@zach-klippenstein )The abstract
ModalContainerand its subclasses (AlertContainer,ModalViewContainer, our internal ones) are all deprecated in favor of a single final class:ModalContainerView, which is able to display any kind ofModalRenderingassociated with aDialogBuilderimplementation.LayoutRunner<T: Any>is deprecated, replaced byViewRunner<T: ViewRendering>. That's going to be a tedious migration for existingLayoutRunnerimplementations, but it avoids a huge breaking change —the new mechanism works in parallel with the deprecated one.
WorkflowViewStub.display(rendering: ViewRendering)replacesWorkflowViewStub.update(rendering: Any), now deprecated. But the latter is able to delegate to the former so incremental migration is possible.