Skip to content
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

Overlay supports unconstrained environments #139513

Merged
merged 7 commits into from Dec 14, 2023

Conversation

goderbauer
Copy link
Member

@goderbauer goderbauer commented Dec 4, 2023

Fixes #137875.

Unfortunately, we cannot auto-detect which OverlayEntry should be sizing the Overlay in unconstrained environment. So, this PR adds a special flag to annotate the Overlay Entry that should be used.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: routes Navigator, Router, and related APIs. labels Dec 8, 2023
@github-actions github-actions bot added the f: material design flutter/packages/flutter/material repository. label Dec 8, 2023
@goderbauer goderbauer marked this pull request as ready for review December 8, 2023 22:42
@goderbauer goderbauer changed the title Overlay supports unconstraind environments Overlay supports unconstrained environments Dec 9, 2023
///
/// Setting this to true has no effect if the OverlayEntry uses a [Positioned]
/// widget to position itself in the Overlay.
final bool canSizeOverlay;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd drop the "can" and just start with "sizes". The word "can" instills doubt. I'm guessing you added it because there's no guarantee that this overlay is the top-most one and so it will not necessarily be used to determine the size of the view. However, that's just a precedence rule or a tie-breaker. It's still OK to say that it sizes the view.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "can" instills doubt.

That's exactly the doubt I want to install because in the vast majority of cases (the Overlay is getting non-infinite constraints) it does not size the overlay. The cases where it does size it are very, very, very rare. I want people to see this property, pause, and read the documentation to learn when it will size the overlay.

/// [BoxConstraints].
///
/// Setting this to true has no effect if the OverlayEntry uses a [Positioned]
/// widget to position itself in the Overlay.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's pretty clear what "Overlay" and "OverlayEntries" are referring to, so I'd just use the plain English phrases "overlay" and "overlay entries" to refer to them. Otherwise, without the [...] they are kind of in limbo state, neither proper symbol links, nor proper nouns.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, updated.

// Same BoxConstraints as used by RenderStack for StackFit.expand.
final BoxConstraints nonPositionedChildConstraints = BoxConstraints.tight(constraints.biggest);
void layoutChild(RenderBox child, BoxConstraints nonPositionedChildConstraints) {
final StackParentData childParentData = child.parentData! as StackParentData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has it been asserted elsewhere that child.parentData vends StackParentData? If not, consider asserting with a descriptive error message prior to unboxing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's guaranteed to be StackParentData here and using it this way is the established pattern in the framework.

'itself to one of its children, but no suitable non-positioned child that has '
'OverlayEntry.canSizeOverlay set to true could be found.',
),
ErrorHint('Try wrapping the Overlay in a SizedBox to give it a finite size.'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or adding a overlay entry with canSizeOverlay set to true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

if (constraints.biggest.isFinite) {
size = constraints.biggest;
} else {
sizeDeterminingChild = _findSizeDeterminingChild();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we memoize the sizeDeterminingChild from the computeDryLayout invocation? I can imagine two issues with the code as is:

  1. Due to a bug _findSizeDeterminingChild() returns different children and the code will keep on trucking, sizing itself incorrectly.
  2. It looks up the same child twice, while a single look-up would suffice. Of course, in debug mode we could assert that the child hasn't changed since the last look-up.

If we go with memoization, we'd probably want to null out the memoized child after the layout is done for this frame, because it should be legal to change the child in the next frame.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "dry" part in computeDryLayout means that the layout should be computed without changing any internal state. There is no guarantee that performLayout is called after computeDryLayout or that it is called with the same constraints or that the RenderObject is still in the same state when it is called and the memoized child is still valid. Of course, we could add caching logic that is invalidated when necessary, but let's defer adding more complexity to this until we see an actual performance problem with this (given that there are only every very few children in a overlay I doubt that there is much performance gain to be had here).

@@ -1602,6 +1602,20 @@ void main() {
expect(find.byType(AnimatedTheme), findsNothing);
expect(find.byType(Theme), findsOneWidget);
});

testWidgets('MaterialApp works in an unconstrained environment', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/137875.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd move this above the testWidgets and reduce the number of empty lines in the body of the test.

@@ -1593,6 +1593,177 @@ void main() {
expect(find.text('Bye, bye'), findsOneWidget);
expect(tester.state(find.byType(Overlay)), same(overlayState));
});

testWidgets('Overlay.wrap works in an unconstrained environment', (WidgetTester tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider being more specific than "works". For example, "sizes to its child".

expect(tester.getSize(find.byType(Overlay)), const Size(123, 456));
});

testWidgets('Overlay works in an unconstrained environment', (WidgetTester tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto re "works"

expect(find.text('World'), findsNothing);
});

testWidgets('Overlay throws if unconstrained and no child', (WidgetTester tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "and has no"

'The constraints given to the overlay ($constraints) would result in an illegal '
'infinite size (${constraints.biggest}). To avoid that, the Overlay tried to size '
'itself to one of its children, but no suitable non-positioned child that has '
'OverlayEntry.canSizeOverlay set to true could be found.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last part of the sentence was confusing to me. "child that has OverlayEntry.canSizeOverlay set to true" sounds wrong because the positioned child is not an OverlayEntry so it can't have OverlayEntry.canSizeOverlay set to true. How about "but the overlay did not contain an overlay entry with a non-positioned child"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased this a bit to hopefully remove that confusion. Just having a non-positioned child is not enough, though, it has to belong to an OverlayEntry that also sets canSizeOverlay. Hope that's clearer now.

/// an infinite size, it has to rely on one of its children to size itself. In
/// this situation, the Overlay will consult the topmost [OverlayEntry] that
/// has this property set to true, lay it out with unconstrained
/// [BoxConstraints], and force all other OverlayEntries to have the same
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the OverlayEntry is positioned?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, updated this to make that clearer.

/// [BoxConstraints], and force all other OverlayEntries to have the same
/// size.
///
/// OverlayEntries that set this to true must be able to handle unconstrained
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: determining whether an OverlayEntry can handle unconstrained BoxConstraints sounds a bit challenging in general? Right now it seems it's always set to true for opaque routes,
so I guess the idea is to trust developers to not put widgets that want to take the full width/height, into a dynamically sized view, without applying extra constraints? If they did then RenderBox.setter should be able to provide actionable feedback?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, all of this is true, assuming you mean the RenderBox.size setter.

/// constraints to be as large as possible. However, if that would result in
/// an infinite size, it has to rely on one of its children to size itself. In
/// this situation, the Overlay will consult the topmost [OverlayEntry] that
/// has this property set to true, lay it out with unconstrained
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not necessarily completely unconstrained I think? Maybe it's just infinite maxWidth or infinite maxHeight or infinite both? I feel unconstrained [BoxConstraints] sounded like we are going to throw the incoming constraints away.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Can a dynamically sized view give you constrained width but unconstrained height?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel unconstrained [BoxConstraints] sounded like we are going to throw the incoming constraints away.

Oh, good catch. I'll update the docs to make it clear that we just pass on the incoming constraints in that case.

(Can a dynamically sized view give you constrained width but unconstrained height?)

Yes, this is allowed.

/// In most situations the Overlay sizes itself based on its incoming
/// constraints to be as large as possible. However, if that would result in
/// an infinite size, it has to rely on one of its children to size itself. In
/// this situation, the Overlay will consult the topmost [OverlayEntry] that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should non-positioned be mentioned here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it for clarity.

@override
void performLayout() {
RenderBox? sizeDeterminingChild;
if (constraints.biggest.isFinite) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be constraints.isTight? Otherwise, will this allow the user to give the view the flexibility to size itself, but also set a limit to make sure the size doesn't blow up? If I'm reading it correctly, if the constraints are limited in any way then we'll force it to take up all the available space, even if biggest is overkill for the content that's shown.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading it correctly, if the constraints are limited in any way then we'll force it to take up all the available space, even if biggest is overkill for the content that's shown.

Yes, that's existing behavior and a lot of things would likely break if we change that :)

@goderbauer goderbauer added the autosubmit Merge PR when tree becomes green via auto submit App label Dec 14, 2023
@auto-submit auto-submit bot merged commit 4162272 into flutter:master Dec 14, 2023
68 checks passed
@goderbauer goderbauer deleted the dynamically-sized-overlay branch December 14, 2023 00:25
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 14, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 14, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 14, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 14, 2023
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Dec 14, 2023
flutter/flutter@11a9cb7...a51e33a

2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from 223f4b2465dd to caf33276468b (1 revision) (flutter/flutter#140156)
2023-12-14 engine-flutter-autoroll@skia.org Roll Packages from b5958e2 to 1151191 (10 revisions) (flutter/flutter#140154)
2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from a3f9393f9591 to 223f4b2465dd (1 revision) (flutter/flutter#140151)
2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from 913446eca57c to a3f9393f9591 (2 revisions) (flutter/flutter#140144)
2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from 923f9e29d4b5 to 913446eca57c (1 revision) (flutter/flutter#140132)
2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from 9f7004e3e30e to 923f9e29d4b5 (7 revisions) (flutter/flutter#140130)
2023-12-14 magder@google.com Add self back to CODEOWNERS (flutter/flutter#140080)
2023-12-14 git@reb0.org Adapt wording for required Android SDK for plugins (flutter/flutter#140043)
2023-12-14 andrewrkolos@gmail.com [reland] Support conditional bundling of assets based on `--flavor` (flutter/flutter#139834)
2023-12-14 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Roll Flutter Engine from 9f7004e3e30e to 45b95f264d63 (1 revision)" (flutter/flutter#140123)
2023-12-14 dacoharkes@google.com [deps] update Android SDK to 34 (flutter/flutter#138183)
2023-12-14 engine-flutter-autoroll@skia.org Roll Flutter Engine from 9f7004e3e30e to 45b95f264d63 (1 revision) (flutter/flutter#140108)
2023-12-14 31859944+LongCatIsLooong@users.noreply.github.com Catch `Stopwatch` with static analysis (flutter/flutter#140019)
2023-12-14 goderbauer@google.com Overlay supports unconstrained environments (flutter/flutter#139513)
2023-12-14 49699333+dependabot[bot]@users.noreply.github.com Bump github/codeql-action from 2.22.10 to 3.22.11 (flutter/flutter#140087)
2023-12-14 rmolivares@renzo-olivares.dev Remove deprecated `ThemeData.selectedRowColor` (flutter/flutter#139080)
2023-12-14 15619084+vashworth@users.noreply.github.com Unpin mac_toolchain version (flutter/flutter#139938)
2023-12-14 chingjun@google.com Optimize file transfer when using proxied devices. (flutter/flutter#139968)
2023-12-14 38378650+hgraceb@users.noreply.github.com Add commonly used parameter names (flutter/flutter#140027)
2023-12-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from fc3267724e1b to 9f7004e3e30e (4 revisions) (flutter/flutter#140090)
2023-12-13 737941+loic-sharma@users.noreply.github.com [Windows] Remove header guard from generated key map (flutter/flutter#140082)
2023-12-13 reidbaker@google.com Do not use project in do last (flutter/flutter#139325)
2023-12-13 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#140024)
2023-12-13 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Warn when Gradle plugins are applied using the legacy "apply script method" way" (flutter/flutter#140102)
2023-12-13 58190796+MitchellGoodwin@users.noreply.github.com Swap iOS back button icon in Material app bar (flutter/flutter#134754)
2023-12-13 engine-flutter-autoroll@skia.org Roll Packages from 80aa46a to b5958e2 (10 revisions) (flutter/flutter#140069)
2023-12-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from 9039ac78cf03 to fc3267724e1b (26 revisions) (flutter/flutter#140084)
2023-12-13 barpac02@gmail.com Warn when Gradle plugins are applied using the legacy "apply script method" way (flutter/flutter#139690)
2023-12-13 magder@google.com Add self as bundler dependabot reviewer (flutter/flutter#140081)
2023-12-13 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Make tests more resilient to Skia gold failures and refactor flutter_goldens for extensive technical debt removal" (flutter/flutter#140085)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC dit@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Michal-MK pushed a commit to Michal-MK/flutter that referenced this pull request Jan 8, 2024
Fixes flutter#137875.

Unfortunately, we cannot auto-detect which OverlayEntry should be sizing the Overlay in unconstrained environment. So, this PR adds a special flag to annotate the Overlay Entry that should be used.
Markzipan pushed a commit to Markzipan/flutter that referenced this pull request Jan 9, 2024
Fixes flutter#137875.

Unfortunately, we cannot auto-detect which OverlayEntry should be sizing the Overlay in unconstrained environment. So, this PR adds a special flag to annotate the Overlay Entry that should be used.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App f: material design flutter/packages/flutter/material repository. f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MaterialApp should support unconstrained environments
3 participants