Fix keepalive for large jumps on tabs and lists.#21350
Fix keepalive for large jumps on tabs and lists.#21350dnfield merged 25 commits intoflutter:masterfrom
Conversation
|
@marekchen - no. The bug you're reporting there is not related to this issue. |
|
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
|
CLAs look good, thanks! |
|
/cc @goderbauer - I'm seeing behavior occasionally where code added in #16445 is getting called after This should still be ok because of the code I introduced in sliver.dart, which should correct the bad parent data a bit later in the whole process if necessary. |
| await tester.pumpAndSettle(); | ||
| await tester.pump(); | ||
| expect(controller.index, 3); | ||
| expect(find.text('4'), findsOneWidget); |
There was a problem hiding this comment.
maybe assert at the beginning that text('4') didn't find anything to ensure that the setup is and stays correct? And assert at the end that AlwaysKeepAlive still finds something?
| return new Container( | ||
| child: new Text('$i'), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Revert the whitespace-only change? Adding a comma after } would be the correct Flutter-style.
| log.clear(); | ||
| }); | ||
|
|
||
| testWidgets('ListView large scroll jump and keepAlive (first child not keepAlive', (WidgetTester tester) async { |
| child: new Directionality( | ||
| textDirection: TextDirection.ltr, | ||
| child: new ListView( | ||
| itemExtent: null, |
There was a problem hiding this comment.
nit: remove itemExtent as null is the default?
| @override | ||
| Widget build(BuildContext context) { | ||
| return new GestureDetector( | ||
| onTap: () => setState, |
There was a problem hiding this comment.
Why this? No state seems to be changing?
There was a problem hiding this comment.
Forces a rebuild.
The issue is that on rebuild, an attempt at fixing this was dropping leading widgets with a keepAlive. Rebuild here checks to make sure we're retaining those widgets.
There was a problem hiding this comment.
Makes sense. Probably worth adding a comment here explaining the strange usage of setstate.
| if (after != null) { | ||
| final SliverMultiBoxAdaptorParentData afterParentData = after.parentData; | ||
| final int index = afterParentData.index; | ||
| if (_keepAliveBucket.containsKey(index)) { |
There was a problem hiding this comment.
rather than looking up the index in the map, i think you can just check _keptAlive on the child's parentData (then assert that it's in the map)
There was a problem hiding this comment.
(while you're at it, we should probably change _createOrObtainChild to do that too)
There was a problem hiding this comment.
_createOrObtainChild gets an index rather than a child. Not sure that one makes sense to change to me. But this one does make sense.
|
This change seems wrong to me. Suppose you keep alive almost every child. When you try to insert a child that used to be after another child that is now itself kept alive, this code is going to insert every child back into the tree in an O(N^2) loop (each insert calls insert for every previous child, and does a loop over the children to find where to insert it). Why are we trying to insert something after a child that is being kept alive? That seems like the real problem here. We're not supposed to ever be passing in an |
That's not a good idea for a sliverlist anyway - the whole point there is to be efficient with memory usage. Keeping almost every child alive won't offer you that benefit.
That is a worst case scenario, yes.
The basic problem is that the The only way I can think of, short of the potentially costly search, is to have The bottom line seems to be that |
|
One small enhancement might be to check if the |
|
macOS failure is a Dart VM garbage collection bug - dart-lang/sdk#33111 |
|
After offline conversation, this is the better fix @Hixie was looking for.
|
|
Has this fix been implemented? Which version of flutter will I need? |
|
@Steelsmasher it should be in master before much longer, and dev a little while after that. |
|
|
||
| /// Whether the widget is currently being kept alive, i.e. has [keepAlive] set | ||
| /// to true and is offscreen. | ||
| bool get keptAlive => _keptAlive; |
There was a problem hiding this comment.
normally we put the public getter above the private field and only have docs for the public getter. your call as to whether this case warrants an exception.
| // build of this subtree. Wait until the end of the frame to update | ||
| // the child when the child is guaranteed to be present. | ||
| SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { | ||
| // The element is no longer alive |
There was a problem hiding this comment.
End comments with a period. Also since the comment is outside the if it should be a question ("Is the element still alive?"), or you can put it inside the block, or you can just remove it, the code speaks for itself here and the comment isn't really adding anything.
Fixes #11895.
Successor to #21207
The SliverMultiBoxAdaptorElement tries to keep track of the child it should be inserting children after when it needs to do an insertion. This breaks if the child it thinks it wants doesn't know what siblings it should have because it got thrown into the keepAliveBucket at some point - the insertion logic expects children to be connected and in the proper indexing order.
This pull makes sure that if the child you want to insert after is in the keep alive bucket, it's removed and reinserted to properly get connected to it's siblings. This case happens in, for example, a multi tab view where the first tab is kept alive and you immediately go to tab 4 (but not if you go from 1 to 2). It also manifests in a sliver list where you fling and then rebuild the list.