Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upFix excessive rebuilds of DSS #69724
Conversation
| )); | ||
| expect(buildCount, 1); | ||
| await tester.flingFrom(const Offset(0, 325), const Offset(0, -325), 200); | ||
| expect(buildCount, 1); |
dnfield
Nov 3, 2020
Author
Member
Before this change, the buildCount was coming out to 48.
Before this change, the buildCount was coming out to 48.
| @@ -317,13 +323,13 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> { | |||
| } | |||
| _extent._currentExtent.value = _extent.initialExtent; | |||
| } | |||
| _child = widget.builder(context, _scrollController); | |||
goderbauer
Nov 4, 2020
Member
What if the DraggableScrollableSheet is given a new builder during its lifetime?
What if the DraggableScrollableSheet is given a new builder during its lifetime?
goderbauer
Nov 4, 2020
Member
Unrelated: Looks like changes to minChildSize and maxChildSize are also not respected, which seems like another bug?
Unrelated: Looks like changes to minChildSize and maxChildSize are also not respected, which seems like another bug?
dnfield
Nov 5, 2020
Author
Member
I think I can address this with didUpdateWidget - will push something momentarily.
I think I can address this with didUpdateWidget - will push something momentarily.
goderbauer
Nov 5, 2020
Member
I seem to recall that a builder is best called on every build (that's what the canonical Builder widget does at least). I don't remember why, though. /cc @Hixie
I seem to recall that a builder is best called on every build (that's what the canonical Builder widget does at least). I don't remember why, though. /cc @Hixie
dnfield
Nov 5, 2020
Author
Member
In this case, that's too much - so I'm trying to cache the output of that builder to avoid rebuilding it whenever the sheet is dragged up or down.
This is simlar to how we don't rebuild every child sliver when a listview scrolls.
In this case, that's too much - so I'm trying to cache the output of that builder to avoid rebuilding it whenever the sheet is dragged up or down.
This is simlar to how we don't rebuild every child sliver when a listview scrolls.
goderbauer
Nov 6, 2020
Member
Sounds good.
Sounds good.
|
Conceptually, could you have the DraggableScrollableSheet build an intermediate widget that takes the prebuild child widget. The intermediate widget then contains the layoutbuilder and the logic to frequently rebuild, but since it is configured with a prebuild child, the child wouldn't build again? Something like: class DraggableScrollableSheet extends StatelessWidget {
DraggableScrollableSheet(
// ...
this.builder,
);
// ..
Widget build(BuildCOntext context) {
return _Intermediate(
//..
child: builder(context),
);
}_Intermediate is basically what DraggableScrollableSheet is today with a child instead of a builder param. Only difficulty: We'd have to move ownership of the scroll controller out of the _Intermediate widget to this new DraggableScrollableSheet since the builder needs it as argument. That doesn't seem impossible, though? |
|
Can't I just update the child in |
|
@goderbauer added some tests that failed based on your suggestion, fixed them with didUpdateWidget - nice catch on the min/max/initial extent too. PTAL. |
| _child = widget.builder(context, _scrollController); | ||
| } | ||
|
|
||
| _extent = _DraggableSheetExtent( |
dnfield
Nov 5, 2020
Author
Member
This is cheap to create, so it seems silly to check the min/max/initial sizes for changes.
This is cheap to create, so it seems silly to check the min/max/initial sizes for changes.
goderbauer
Nov 6, 2020
Member
Is this still true even with us now creating a new _scrollController as well, which will cause other widgets to move their listeners over to the new one?
Is this still true even with us now creating a new _scrollController as well, which will cause other widgets to move their listeners over to the new one?
dnfield
Nov 6, 2020
Author
Member
I think it's probably still fine
I think it's probably still fine
| _child = widget.builder(context, _scrollController); | ||
| } | ||
|
|
||
| _extent = _DraggableSheetExtent( |
goderbauer
Nov 5, 2020
Member
How is the new extend wired up to the _DraggableScrollableSheetScrollController?
How is the new extend wired up to the _DraggableScrollableSheetScrollController?
dnfield
Nov 5, 2020
Author
Member
Fixed
Fixed
| @@ -317,13 +323,13 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> { | |||
| } | |||
| _extent._currentExtent.value = _extent.initialExtent; | |||
| } | |||
| _child = widget.builder(context, _scrollController); | |||
goderbauer
Nov 5, 2020
Member
I seem to recall that a builder is best called on every build (that's what the canonical Builder widget does at least). I don't remember why, though. /cc @Hixie
I seem to recall that a builder is best called on every build (that's what the canonical Builder widget does at least). I don't remember why, though. /cc @Hixie
| @override | ||
| void didUpdateWidget(DraggableScrollableSheet oldWidget) { | ||
| super.didUpdateWidget(oldWidget); | ||
| if (oldWidget.builder != widget.builder) { |
goderbauer
Nov 5, 2020
Member
If anything, I think you need to call the builder even if the closure has the same identity. For example, if somebody passes in a tear-off function of a State object, you always get the same builder object, but values closured into that builder may have changed.
If anything, I think you need to call the builder even if the closure has the same identity. For example, if somebody passes in a tear-off function of a State object, you always get the same builder object, but values closured into that builder may have changed.
dnfield
Nov 5, 2020
Author
Member
Done
Done
| @@ -288,17 +288,17 @@ class _DraggableSheetExtent { | |||
| class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> { | |||
| late _DraggableScrollableSheetScrollController _scrollController; | |||
| late _DraggableSheetExtent _extent; | |||
| // The child only gets rebuilt when dependencies change. Otherwise, excessive | |||
goderbauer
Nov 6, 2020
Member
nit: comment is out of date, also rebuilds on didUpdateWidget.
nit: comment is out of date, also rebuilds on didUpdateWidget.
dnfield
Nov 6, 2020
Author
Member
Updated
Updated
| @override | ||
| void didUpdateWidget(DraggableScrollableSheet oldWidget) { | ||
| super.didUpdateWidget(oldWidget); | ||
| _child = widget.builder(context, _scrollController); |
goderbauer
Nov 6, 2020
Member
Maybe add a quick comment here reminding us in the future why we have to call this unconditionally (and not just when builder changes)?
Maybe add a quick comment here reminding us in the future why we have to call this unconditionally (and not just when builder changes)?
dnfield
Nov 6, 2020
Author
Member
Done.
Done.
| void didUpdateWidget(DraggableScrollableSheet oldWidget) { | ||
| super.didUpdateWidget(oldWidget); | ||
| _child = widget.builder(context, _scrollController); | ||
| _updateExtent(); |
goderbauer
Nov 6, 2020
Member
I think _updateExtent has to run before we execute the builder since it instantiates a new scrollController that's used by the builder?
I think _updateExtent has to run before we execute the builder since it instantiates a new scrollController that's used by the builder?
dnfield
Nov 6, 2020
Author
Member
Done.
Done.
| _child = widget.builder(context, _scrollController); | ||
| } | ||
|
|
||
| _extent = _DraggableSheetExtent( |
goderbauer
Nov 6, 2020
Member
Is this still true even with us now creating a new _scrollController as well, which will cause other widgets to move their listeners over to the new one?
Is this still true even with us now creating a new _scrollController as well, which will cause other widgets to move their listeners over to the new one?
| @@ -317,13 +323,13 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> { | |||
| } | |||
| _extent._currentExtent.value = _extent.initialExtent; | |||
| } | |||
| _child = widget.builder(context, _scrollController); | |||
goderbauer
Nov 6, 2020
Member
Sounds good.
Sounds good.
|
LGTM |
16dce76
into
flutter:master
Description
DraggableScrollableSheet internally ends up calling the builder function every time the sheet scrolls - this is really unfortunate when, for example, it contains a sizable ListView that gets rebuilt every time the sheet slides up or down one pixel.
To resolve this, move the call of the builder function to
didChangeDependenciesso it's only called when our position in the tree changes, and reuse the cached child inbuild.Related Issues
Fixes #67219
Tests
Assert that the builder is only called once when dependencies do not change and the sheet is dragged.
This is not a breaking change.