-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to Reproduce
Problem
If the NestedScrollView example from api.flutter.dev is used with material3 the stretch controller throws a Build scheduled during frame error. The error appears when the CustomScrollView is stretched multiple times (pulled down). I think the errors occurs when the dimension is changed and trigger applyNewDimensions -> BallisticScrollActivity, etc, etc,
Error:
Build scheduled during frame.
While the widget tree was being built, laid out, and painted, a new frame was scheduled to rebuild the widget tree.
This might be because setState() was called from a layout or paint callback.
Expected results:
It is expected that this standard code example runs without errors.
Actual results:
If the error occurs the CustomScrollView cannot be stretched anymore until a complete rebuild of the widget. The components in the slivers are also not always completely visible after the error, I think the view is than still partly stretched. :)
Code sample
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
title: _title,
home: const MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<String> tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: tabs.length, // This is the number of tabs.
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title:
const Text('Books'), // This is the title in the app bar.
pinned: true,
expandedHeight: 150.0,
// The "forceElevated" property causes the SliverAppBar to show
// a shadow. The "innerBoxIsScrolled" parameter is true when the
// inner scroll view is scrolled beyond its "zero" point, i.e.
// when it appears to be scrolled below the SliverAppBar.
// Without this, there are cases where the shadow would appear
// or not appear inappropriately, because the SliverAppBar is
// not actually aware of the precise position of the inner
// scroll views.
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return ListTile(
title: Text('Item $index'),
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
Logs
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for _StretchController:
Build scheduled during frame.
While the widget tree was being built, laid out, and painted, a new frame was scheduled to rebuild the widget tree.
This might be because setState() was called from a layout or paint callback. If a change is needed to the widget tree, it should be applied as the tree is being built. Scheduling a change for the subsequent frame instead results in an interface that lags behind by one frame. If this was done to make your build dependent on a size measured at layout time, consider using a LayoutBuilder, CustomSingleChildLayout, or CustomMultiChildLayout. If, on the other hand, the one frame delay is the desired effect, for example because this is an animation, consider scheduling the frame in a post-frame callback using SchedulerBinding.addPostFrameCallback or using an AnimationController to trigger the animation.
When the exception was thrown, this was the stack
#0 WidgetsBinding._handleBuildScheduled.<anonymous closure>
#1 WidgetsBinding._handleBuildScheduled
#2 BuildOwner.scheduleBuildFor
#3 Element.markNeedsBuild
#4 State.setState
#5 _AnimatedState._handleChange
#6 ChangeNotifier.notifyListeners
#7 AnimationLocalListenersMixin.notifyListeners
#8 AnimationController.value=
#9 AnimationController.forward
#10 _StretchController._recede
#11 _StretchController.scrollEnd
#12 _StretchingOverscrollIndicatorState._handleScrollNotification
#13 _NotificationElement.onNotification
#14 _NotificationNode.dispatchNotification
#15 _NotificationNode.dispatchNotification
#16 Element.dispatchNotification
#17 Notification.dispatch
#18 ScrollActivity.dispatchScrollEndNotification
#19 ScrollPosition.didEndScroll
#20 ScrollPosition.beginActivity
#21 _NestedInnerBallisticScrollActivity.applyNewDimensions
#22 ScrollPosition.applyNewDimensions
#23 _NestedScrollPosition.applyNewDimensions
#24 ScrollPosition.applyContentDimensions
#25 RenderViewport.performLayout
#26 RenderObject.layout
#27 RenderProxyBoxMixin.performLayout
#28 RenderObject.layout
#29 RenderProxyBoxMixin.performLayout
#30 RenderObject.layout
#31 RenderProxyBoxMixin.performLayout
#32 RenderObject.layout
#33 RenderProxyBoxMixin.performLayout
#34 RenderObject.layout
#35 RenderProxyBoxMixin.performLayout
#36 RenderObject.layout
#37 RenderProxyBoxMixin.performLayout
#38 RenderObject.layout
#39 RenderProxyBoxMixin.performLayout
#40 RenderObject.layout
#41 RenderProxyBoxMixin.performLayout
#42 _RenderCustomClip.performLayout
#43 RenderObject.layout
#44 RenderPadding.performLayout
#45 RenderObject.layout
#46 RenderProxyBoxMixin.performLayout
#47 RenderObject.layout
#48 RenderProxyBoxMixin.performLayout
#49 RenderObject.layout
#50 RenderSliverFixedExtentBoxAdaptor.performLayout
#51 RenderObject.layout
#52 RenderSliverEdgeInsetsPadding.performLayout
#53 _RenderSliverFractionalPadding.performLayout
#54 RenderObject.layout
#55 RenderViewportBase.layoutChildSequence
#56 RenderViewport._attemptLayout
#57 RenderViewport.performLayout
#58 RenderObject.layout
#59 RenderProxyBoxMixin.performLayout
#60 RenderObject.layout
#61 RenderProxyBoxMixin.performLayout
#62 RenderObject.layout
#63 RenderProxyBoxMixin.performLayout
#64 RenderObject.layout
#65 RenderProxyBoxMixin.performLayout
#66 RenderObject.layout
#67 RenderProxyBoxMixin.performLayout
#68 RenderObject.layout
#69 RenderProxyBoxMixin.performLayout
#70 RenderObject.layout
#71 RenderProxyBoxMixin.performLayout
#72 RenderObject.layout
#73 RenderProxyBoxMixin.performLayout
#74 _RenderCustomClip.performLayout
#75 RenderObject.layout
#76 RenderSliverFillRemainingWithScrollable.performLayout
#77 RenderObject.layout
#78 RenderViewportBase.layoutChildSequence
#79 RenderViewport._attemptLayout
#80 RenderViewport.performLayout
#81 RenderObject._layoutWithoutResize
#82 PipelineOwner.flushLayout
#83 RendererBinding.drawFrame
#84 WidgetsBinding.drawFrame
#85 RendererBinding._handlePersistentFrameCallback
#86 SchedulerBinding._invokeFrameCallback
#87 SchedulerBinding.handleDrawFrame
#88 SchedulerBinding._handleDrawFrame
#92 _invoke (dart:ui/hooks.dart:151:10)
#93 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#94 _drawFrame (dart:ui/hooks.dart:115:31)
(elided 3 frames from dart:async)
The _StretchController sending notification was: _StretchController()
════════════════════════════════════════════════════════════════════════════════
Analyzing flutter_application_1...
info • Unused import: 'package:flutter/material.dart' • lib/test.dart:3:8 • unused_import
[✓] Flutter (Channel stable, 3.0.1, on openSUSE Leap 15.3 5.3.18-150300.59.68-default,
locale en_US.UTF-8)
• Flutter version 3.0.1 at /home/joan/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision fb57da5f94 (7 days ago), 2022-05-19 15:50:29 -0700
• Engine revision caaafc5604
• Dart version 2.17.1
• DevTools version 2.12.2
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
• Android SDK at /home/joan/Android/Sdk
• Platform android-32, build-tools 32.1.0-rc1
• Java binary at: /home/joan/App/android-studio/jre/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
• All Android licenses accepted.
[✓] Chrome - develop for the web
• Chrome at google-chrome
[✓] Linux toolchain - develop for Linux desktop
• clang version 11.0.1
• cmake version 3.17.0
• ninja version 1.10.0
• pkg-config version 0.29.2
[✓] Android Studio (version 2021.2)
• Android Studio at /home/joan/App/android-studio
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin version 212.5744
• Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
[✓] VS Code (version 1.57.0)
• VS Code at /usr/share/code
• Flutter extension version 3.25.0
[✓] Connected device (3 available)
• sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 12 (API
32) (emulator)
• Linux (desktop) • linux • linux-x64 • openSUSE Leap 15.3
5.3.18-150300.59.68-default
• Chrome (web) • chrome • web-javascript • Google Chrome
102.0.5005.61
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!