-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Allow "from" hero state to survive hero animation in a push transition #32842
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
Allow "from" hero state to survive hero animation in a push transition #32842
Conversation
HansMuller
left a comment
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 looks good. Just some trivial formatting feedback.
| setState(() { | ||
| _placeholderSize = box.size; | ||
| }); | ||
| setState(() => _placeholderSize = box.size); |
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.
We usually only use => for one-liners that return a value. That's why the original version of this statement was formatted with { }
Same comment below
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.
Done. But technically the expression evaluates to box.size, and setState does use the returned value (check it's runtime type). Not sure where to draw the line.
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.
It's true that setState() checks the return value, but only to warn the user if they've mistakenly returned a Future. I think one-liners that implement a void method should get curly braced.
| _proxyAnimation.parent = manifest.animation; | ||
|
|
||
| manifest.fromHero.startFlight(); | ||
| manifest.fromHero.startFlight(shouldIncludedChildInPlaceholder: manifest.type == HeroFlightDirection.push); |
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.
We need to be slightly careful here. If the user uses a shuttleBuilder, it could be reasonable for the user to expect that he/she can just use some globalkey that used to be in the from Hero inside the custom shuttleBuilder. That might break if we kept the original tree still under the Hero
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.
going to add a Limitations section in flightShuttleBuilder's documentation. Is this considered a breaking change?
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.
Then you'd have to explain that that's only true for the Hero of the from route in the event of a push navigation. Perhaps add another argument to Hero for preservePlaceholderStateDuringFlight that defaults to true and gets overridden when a custom placeholderBuilder is given?
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 think I did:
/// ## Limitations
///
/// Currently if a widget built by [flightShuttleBuilder] takes part in a
/// [Navigator] push transition, that widget or its descendants must not have
/// any [GlobalKey] that is used in the source Hero's descendant widgets.
Isn't flightShuttleBuilder == null always equal to preservePlaceholderStateDuringFlight?
In that case maybe we should add a Size parameter to placeholderBuilder?
| ? Visibility( | ||
| visible: false, | ||
| maintainState: true, | ||
| maintainAnimation: true, |
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.
ummm this is a bit unfortunate. @HansMuller it's not super clear to me why this is needed looking at the cascading logic in Visibility. Do you know?
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 sure what you mean by "cascading logic". I agree that keeping the from Hero's animation running while it is hidden seems unnecessary.
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 just meant this series of logic
| assert(maintainState == true || maintainAnimation == false, 'Cannot maintain animations if the state is not also maintained.'), |
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.
Replacing the visibility widget with SizedBox(Offstage(TickerMode)) seems to work
xster
left a comment
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.
LGTM
| ? Visibility( | ||
| visible: false, | ||
| maintainState: true, | ||
| maintainAnimation: true, |
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 just meant this series of logic
| assert(maintainState == true || maintainAnimation == false, 'Cannot maintain animations if the state is not also maintained.'), |
| _proxyAnimation.parent = manifest.animation; | ||
|
|
||
| manifest.fromHero.startFlight(); | ||
| manifest.fromHero.startFlight(shouldIncludedChildInPlaceholder: manifest.type == HeroFlightDirection.push); |
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.
Then you'd have to explain that that's only true for the Hero of the from route in the event of a push navigation. Perhaps add another argument to Hero for preservePlaceholderStateDuringFlight that defaults to true and gets overridden when a custom placeholderBuilder is given?
|
Is there a link to the breaking change announcement? |
|
@Piinks there isn't one yet. I'll post the announcement once the changes are finalized. |
HansMuller
left a comment
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.
LGTM - just some suggestions about the docs
| /// [MaterialRectArcTween]. | ||
| typedef CreateRectTween = Tween<Rect> Function(Rect begin, Rect end); | ||
|
|
||
| /// A builder that builds a placeholder widget given a child and a [Size] |
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.
A function that builds a [Hero] placeholder widget ...
| /// A builder that builds a placeholder widget given a child and a [Size] | ||
| /// | ||
| /// The child can optionally be part of the returned widget tree. The returned | ||
| /// widget should typically be sized to [heroSize], if it doesn't do so |
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.
sized => constrained
| /// | ||
| /// If a widget built by [flightShuttleBuilder] takes part in a [Navigator] | ||
| /// push transition, that widget or its descendants must not have any | ||
| /// [GlobalKey] that is used in the source Hero's descendant widgets, as both |
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.
Better to break the "as both" point into a new sentence:
...widgets. Both subtrees will be included ... animation, and global keys must be unique across the entire widget tree.
| /// By default, an empty SizedBox keeping the Hero child's original size is | ||
| /// left in place once the Hero shuttle has taken flight. | ||
| final TransitionBuilder placeholderBuilder; | ||
| /// By default the placeholder widget left in place is an empty [SizedBox] |
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.
Please leave out "left in place"
...placeholder widget is an empty...
| final TransitionBuilder placeholderBuilder; | ||
| /// By default the placeholder widget left in place is an empty [SizedBox] | ||
| /// keeping the Hero child's original size, unless this Hero is a source Hero | ||
| /// of a [Navigator] push transition, in which case the placeholder will be a |
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 run-on. Maybe:
...push transition. If this hero the source of a push transition, the [child] will be a descendant of the placeholder. The child will be kept [Offstage] during the hero's flight.
|
Hey @LongCatIsLooong , this is great. It looks like your branch needs to be updated to fix those codegen errors that are causing test failures. |
| /// [GlobalKey] that is used in the source Hero's descendant widgets. That is | ||
| /// because both subtrees will be included in the widget tree during the Hero | ||
| /// flight animation, and [GlobalKey]s must be unique across the entire widget | ||
| /// tree. |
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 don't understand this. Are you saying the child will be included twice in the tree? Because that won't work if the widget is stateful... the whole point of the Hero logic is that it only be included once.
|
cc @goderbauer |
* master: (24 commits) [flutter_tool,fuchsia] Update the install flow for packaging migration. (flutter#34447) SliverFillRemaining flag for different use cases (flutter#33627) SizedBox documentation (flutter#34424) Change API doc link to api.dart.dev (flutter#34388) 2589785 Roll src/third_party/skia 87e885038893..c3252a04b377 (3 commits) (flutter/engine#9327) (flutter#34484) ace5d59 Fix rawTypes errors in Android embedding classes (flutter/engine#9326) (flutter#34481) bf0def6 Roll src/third_party/skia 4c4945a25248..87e885038893 (1 commits) (flutter/engine#9325) (flutter#34471) Roll engine f1d821d..6f5347c (13 commits) (flutter#34466) Allow "from" hero state to survive hero animation in a push transition (flutter#32842) Roll pub dependencies (flutter#33677) Skip flaky test on Windows (flutter#34464) Allow flaky tests to pass or fail and mark web tests as flaky (flutter#34456) Dont depend on web SDK unless running tests on chrome (flutter#34457) Fix semantics_tester (flutter#34368) Include raw value in Diagnostics json for basic types (flutter#34417) Refactor Gradle plugin (flutter#34353) Allow web tests to fail in cirrus config (flutter#34436) skip bottom_sheet (flutter#34430) Remove unused flag `--target-platform` from `flutter run` (flutter#34369) Extract DiagnosticsNode serializer from WidgetInspector (flutter#34012) ...
| } | ||
|
|
||
| return SizedBox( | ||
| width: _placeholderSize?.width, |
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 breaks getting sematnics for a FloatingActionButton, e.g.
await tester.pumpWidget(MaterialApp(
home: FloatingActionButton(
onPressed: () {},
)));
expect(
tester.getSemantics(find.byType(FloatingActionButton)),
matchesSemantics(
hasTapAction: true,
hasEnabledState: true,
isButton: true,
isEnabled: true,
));
Now returns the semantics node for the parent of the widget rather than the semantics node for the widget.
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.
Fix for this is in #34512
Description
When a route push transition happens,
Herowidgets involved in the animation are all replaced by placeholder widgets (SizedBoxes). This removes their elements (as well as state) from the tree which might not be desirable for the fromHeroes if they are stateful.Related Issues
Fixes #31503 and #32356, somewhat mitigates #31473
Tests
I added the following tests:
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?