-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Keep alive support for 2D scrolling #131641
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
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 have a more general question. I assume this class will be extended by a first party package?
In that case why isn't the two_dimentional_viewport.dart in that package instead of here?
| visitor(child); | ||
| child = parentDataOf(child)._nextSibling; | ||
| } | ||
| _keepAliveBucket.values.forEach(visitor); |
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 am slightly surprised keptalive children are visited as well in this method. I did see the slivermultiboxadapter also does this, but I am not entirely sure why.
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 also am not aware of why. I traced it back to #11010 when keep alive as first added. I think of it being kind of similar to the Offstage widget. The child is kept alive without painting anything, without making the child available for hit testing, and without taking any room in the parent. However, Offstage children are still active: they can receive focus and have keyboard input directed to them.
| _keepAliveBucket.values.forEach(visitor); | ||
| } | ||
|
|
||
| @override |
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 looks like visitChildrenForSemantics only visits children that are visible, how does it work if user trying to visit offscreen item (thus will cause scroll)?
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.
Trying to remember... I think this was because ensureVisible/getOffsetToReveal is not yet implemented for RenderTwoDimensionalViewport. That is in #128812. Otherwise right now it would crash, so I think I remember putting the visible part in place to prevent a crash until it was properly implemented. I almost forgot about it, I added a note to the PR for when I get back to it.
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.
Can you add a todo to here just to make sure we don't forget it once we implemented these methods?
| } | ||
| // If the child in the bucket is not current child, that means someone has | ||
| // already moved and replaced current child, and we cannot remove this | ||
| // child. |
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 this may only possible if the earlier movechild move another child into this place. In this case this child will be in _debugDanglingKeepAlives. Maybe add an assert check here?
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.
For this and the comment below, this matches almost exactly to the implementation in RenderSliverMultiBoxAdaptor, should the same changes be made there? I traced this back to #31978 for context.
| return; | ||
| } | ||
| assert(_debugTrackOrphans(noLongerOrphan: child)); | ||
| assert(_keepAliveBucket[childParentData.vicinity] == child); |
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.
if they are in _debugDanglingKeepAlives they won't be in here, no?
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.
They should be. Every child in _debugDanglingKeepAlives should be in the _keepAliveBucket, but not necessarily the other way around.
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.
you are right, I misread the code
|
|
||
| void _insertChild(RenderBox child, ChildVicinity slot) { | ||
| assert(_debugTrackOrphans(newOrphan: _children[slot])); | ||
| assert(!_keepAliveBucket.containsValue(child)); |
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.
also check _debugDanglingKeepAlives ?
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.
Should it be empty, or not contain the child? We do not have a similar check in RenderSliverMultiBoxAdaptor, should I add one there too? RenderTwoDimensionalViewport is like a cross between RenderSliverMultiBoxAdaptor and Viewport with two offsets, so I have been trying to match behavior as much as possible for consistency. :)
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 they should not contain the child because it should be removed from the _debugDanglingKeepAlives.
Given this is mirroring the 1D case, I am fine either you add both or not.
Can also think about extracting a mixin with generic slot type if things start to get repeated.
| /// Returns the child for a given [ChildVicinity]. | ||
| /// | ||
| /// This method will build the child if it has not been already, or will reuse | ||
| /// it if it already exists. |
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 method can have more detail doc string. Maybe add some comment around when this method should be call (in layoutChildSequence I think?) and the childvicinity must be what is visible on the screen because this method will insert the child into active children list
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.
The children aren't required to be visible since they could be in the cache extent and are ok as active children there. I will add more docs though.
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.
The comment seems not updated yet?
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.
Ah thank you!
Piinks
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.
Thanks for review! Good questions!
I assume this class will be extended by a first party package?
Yup! The first version is in flutter/packages#4536
In that case why isn't the two_dimensional_viewport.dart in that package instead of here?
A couple of reasons. The main one being that the 2D foundation must be in the framework due to some overrides of private API in Scrollable.
Additionally, there is a lot of scrolling functionality built-in to other widgets in the framework, and we want them all to work the same whether it is 1D or 2D.
ensureVisible is a good example, which supports default focus traversal. We would not want developers to have to call ensureVisible for 1D and then some other method for 2D, on top of the fact that several places in the framework call this and we want it to just work rather than having two different code paths.
Implementing the core functionality of 2D in the framework and providing base classes for developers to build on top of was the general idea. The 1P packages we are building on top of it in flutter/packages may move into the framework later, but we wanted to be able to iterate on them more rapidly from the package repo first.
| visitor(child); | ||
| child = parentDataOf(child)._nextSibling; | ||
| } | ||
| _keepAliveBucket.values.forEach(visitor); |
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 also am not aware of why. I traced it back to #11010 when keep alive as first added. I think of it being kind of similar to the Offstage widget. The child is kept alive without painting anything, without making the child available for hit testing, and without taking any room in the parent. However, Offstage children are still active: they can receive focus and have keyboard input directed to them.
| _keepAliveBucket.values.forEach(visitor); | ||
| } | ||
|
|
||
| @override |
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.
Trying to remember... I think this was because ensureVisible/getOffsetToReveal is not yet implemented for RenderTwoDimensionalViewport. That is in #128812. Otherwise right now it would crash, so I think I remember putting the visible part in place to prevent a crash until it was properly implemented. I almost forgot about it, I added a note to the PR for when I get back to it.
| /// Returns the child for a given [ChildVicinity]. | ||
| /// | ||
| /// This method will build the child if it has not been already, or will reuse | ||
| /// it if it already exists. |
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.
The children aren't required to be visible since they could be in the cache extent and are ok as active children there. I will add more docs though.
|
|
||
| void _insertChild(RenderBox child, ChildVicinity slot) { | ||
| assert(_debugTrackOrphans(newOrphan: _children[slot])); | ||
| assert(!_keepAliveBucket.containsValue(child)); |
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.
Should it be empty, or not contain the child? We do not have a similar check in RenderSliverMultiBoxAdaptor, should I add one there too? RenderTwoDimensionalViewport is like a cross between RenderSliverMultiBoxAdaptor and Viewport with two offsets, so I have been trying to match behavior as much as possible for consistency. :)
| } | ||
| // If the child in the bucket is not current child, that means someone has | ||
| // already moved and replaced current child, and we cannot remove this | ||
| // child. |
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.
For this and the comment below, this matches almost exactly to the implementation in RenderSliverMultiBoxAdaptor, should the same changes be made there? I traced this back to #31978 for context.
| return; | ||
| } | ||
| assert(_debugTrackOrphans(noLongerOrphan: child)); | ||
| assert(_keepAliveBucket[childParentData.vicinity] == child); |
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.
They should be. Every child in _debugDanglingKeepAlives should be in the _keepAliveBucket, but not necessarily the other way around.
chunhtai
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 left some minor comments
| _keepAliveBucket.values.forEach(visitor); | ||
| } | ||
|
|
||
| @override |
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.
Can you add a todo to here just to make sure we don't forget it once we implemented these methods?
| /// Returns the child for a given [ChildVicinity]. | ||
| /// | ||
| /// This method will build the child if it has not been already, or will reuse | ||
| /// it if it already exists. |
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.
The comment seems not updated yet?
| _childManager._buildChild(vicinity); | ||
| }); | ||
| } else { | ||
| if (_keepAliveBucket.containsKey(vicinity)) { |
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 this check needed? I think you can just remove even if it is not in the map?
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.
Oh right. Good call.
|
|
||
| void _insertChild(RenderBox child, ChildVicinity slot) { | ||
| assert(_debugTrackOrphans(newOrphan: _children[slot])); | ||
| assert(!_keepAliveBucket.containsValue(child)); |
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 they should not contain the child because it should be removed from the _debugDanglingKeepAlives.
Given this is mirroring the 1D case, I am fine either you add both or not.
Can also think about extracting a mixin with generic slot type if things start to get repeated.
| return; | ||
| } | ||
| assert(_debugTrackOrphans(noLongerOrphan: child)); | ||
| assert(_keepAliveBucket[childParentData.vicinity] == child); |
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.
you are right, I misread the code
|
auto label is removed for flutter/flutter/131641, due to - The status or check suite Linux web_tests_7_last has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
Oh whoops I broke something |
|
Yay for tests. 😍 |
Fixes #126297
This adds support for keep alive to the 2D scrolling foundation. The TwoDimensionalChildBuilderDelegate and TwoDimensionalChildListDelegate will both add automatic keep alives to their children, matching the convention from SliverChildDelegates. The TwoDimensionalViewportParentData now incorporates keep alive and which is managed by the RenderTwoDimensionalViewport.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.