Skip to content

NestedScrollView / Material3: _StretchController throws the error: Build scheduled during frame #104798

@JoanSchi

Description

@JoanSchi

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: error messageError messages from the Flutter frameworkf: material designflutter/packages/flutter/material repository.f: scrollingViewports, list views, slivers, etc.found in release: 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-designOwned by Design Languages teamtriaged-designTriaged by Design Languages teamworkaround availableThere is a workaround available to overcome the issue

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions