flutternestedscrollviewflutter-sliverappbar

Flutter NestedScrollView ScrollController is currently attached to more than one ScrollPosition


I am building a Flutter App and on the Web with a SliverAppBar with Tabs that are scrollable using a NestedScrollView and I am getting this weird error:

The Scrollbar requires a single ScrollPosition in order to be painted.
When the scrollbar is interactive, the associated ScrollController must only have one ScrollPosition
attached. The provided ScrollController must be unique to one ScrollView widget.

The error occurs when I scroll on one tab and then move to another tab, then move back to the previous tab before it has fully changed tabs.

I have tried adding a scrollcontroller to the CustomScrollView widgets. I have also tried

primary: false;

However, this just makes it so my SliverAppBar no longer scrolls up with the body and in some cases still has the same error.

PLEASE DO NOT JUST TELL ME TO ADD SCROLLCONTROLLER() TO MY CUSTOMSCROLLVIEW WIDGETS, THIS IS NOT HELPFUL.

Here is my minimal code with the error happening:

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);

  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  SliverAppBar showSliverAppBar(BuildContext context, String screenTitle) {
    final screenSize = MediaQuery.of(context).size;

    return SliverAppBar(
      elevation: 15,
      backgroundColor: Theme.of(context).colorScheme.primary,
      floating: true,
      pinned: true,
      snap: false,
      centerTitle: true,
      title: Center(
        child: Text(
          screenTitle,
          textAlign: TextAlign.center,
        ),
      ),
      actions: [
        Padding(
          padding: EdgeInsets.only(
            right: screenSize.width * 0.01,
          ),
          child: IconButton(
            onPressed: () {},
            icon: const Icon(
              Icons.person,
            ),
          ),
        ),
      ],
      bottom: const TabBar(
        tabs: [
          Tab(
            icon: Icon(Icons.home),
            text: 'Home',
          ),
          Tab(
            icon: Icon(Icons.calendar_month_outlined),
            text: 'Calendar',
          ),
          Tab(
            icon: Icon(Icons.question_mark),
            text: 'About Us',
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: const Drawer(
        child: Column(
          children: <Widget>[
            DrawerHeader(
              curve: SawTooth(12),
              child: Text('I am Drawer'),
            ),
          ],
        ),
      ),
      body: DefaultTabController(
        length: 3,
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return [
              showSliverAppBar(context, 'Nested Scroll View Error'),
            ];
          },
          body: TabBarView(
            children: [
              CustomScrollView(
                slivers: [
                  SliverList(
                    delegate: SliverChildListDelegate(
                      [
                        Container(
                          height: 600,
                          color: Theme.of(context).colorScheme.secondary,
                          child: const Center(
                            child: Text(
                              'Calendar Tab',
                              style: TextStyle(fontSize: 40),
                            ),
                          ),
                        ),
                        Container(
                          height: 1500,
                          color: Colors.green,
                          child: Center(
                            child: _widgetOptions.elementAt(_selectedIndex),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              CustomScrollView(
                slivers: [
                  SliverList(
                    delegate: SliverChildListDelegate(
                      [
                        Container(
                          height: 600,
                          color: Colors.blue[200],
                          child: const Center(
                            child: Text('Calendar Tab',
                                style: TextStyle(fontSize: 40)),
                          ),
                        ),
                        Container(
                          height: 1200,
                          color: Colors.pink,
                          child: Center(
                            child: _widgetOptions.elementAt(_selectedIndex),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              CustomScrollView(
                slivers: [
                  SliverList(
                    delegate: SliverChildListDelegate(
                      [
                        Container(
                          height: 600,
                          color: Colors.blue[200],
                          child: const Center(
                            child: Text('About Us Tab',
                                style: TextStyle(fontSize: 40)),
                          ),
                        ),
                        Container(
                          height: 1200,
                          color: Colors.pink,
                          child: Center(
                            child: _widgetOptions.elementAt(_selectedIndex),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: 'School',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

Solution

  • I found the issue. It turns out that a tabView with scrollbars causes the issue.

    On desktop scrollbars are automatically added, this includes on Chrome. I was only experiencing this issue on desktop. source: https://docs.flutter.dev/release/breaking-changes/default-desktop-scrollbars

    It seems to be related to this: https://github.com/flutter/flutter/pull/78588

    To fix this issue I needed to make it so scrollBars were turned off in my application. So I went to my main.dart and inside of MaterialApp I added scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false). This simple change disabled scrollbars and fixed the issue. I have also found that if I do not want to disable scroll bars in my entire app, I can go into the individual CustomScrollView Widgets and add scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false) and that would fix the error also.

    Fixed code:

    import 'package:flutter/material.dart';
    
    class HomePage extends StatefulWidget {
      const HomePage({Key? key}) : super(key: key);
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      int _selectedIndex = 0;
    
      void _onItemTapped(int index) {
        setState(() {
          _selectedIndex = index;
        });
      }
    
      static const TextStyle optionStyle =
          TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
    
      static const List<Widget> _widgetOptions = <Widget>[
        Text(
          'Index 0: Home',
          style: optionStyle,
        ),
        Text(
          'Index 1: Business',
          style: optionStyle,
        ),
        Text(
          'Index 2: School',
          style: optionStyle,
        ),
      ];
    
      SliverAppBar showSliverAppBar(BuildContext context, String screenTitle) {
        final screenSize = MediaQuery.of(context).size;
    
        return SliverAppBar(
          elevation: 15,
          backgroundColor: Theme.of(context).colorScheme.primary,
          floating: true,
          pinned: true,
          snap: false,
          centerTitle: true,
          title: Center(
            child: Text(
              screenTitle,
              textAlign: TextAlign.center,
            ),
          ),
          actions: [
            Padding(
              padding: EdgeInsets.only(
                right: screenSize.width * 0.01,
              ),
              child: IconButton(
                onPressed: () {},
                icon: const Icon(
                  Icons.person,
                ),
              ),
            ),
          ],
          bottom: const TabBar(
            tabs: [
              Tab(
                icon: Icon(Icons.home),
                text: 'Home',
              ),
              Tab(
                icon: Icon(Icons.calendar_month_outlined),
                text: 'Calendar',
              ),
              Tab(
                icon: Icon(Icons.question_mark),
                text: 'About Us',
              ),
            ],
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          drawer: const Drawer(
            child: Column(
              children: <Widget>[
                DrawerHeader(
                  curve: SawTooth(12),
                  child: Text('I am Drawer'),
                ),
              ],
            ),
          ),
          body: DefaultTabController(
            length: 3,
            child: NestedScrollView(
              headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
                return [
                  showSliverAppBar(context, 'Nested Scroll View Error'),
                ];
              },
              body: TabBarView(
                children: [
                  CustomScrollView(
                    scrollBehavior:
                        ScrollConfiguration.of(context).copyWith(scrollbars: false),
                    slivers: [
                      SliverList(
                        delegate: SliverChildListDelegate(
                          [
                            Container(
                              height: 600,
                              color: Theme.of(context).colorScheme.secondary,
                              child: const Center(
                                child: Text(
                                  'Calendar Tab',
                                  style: TextStyle(fontSize: 40),
                                ),
                              ),
                            ),
                            Container(
                              height: 1500,
                              color: Colors.green,
                              child: Center(
                                child: _widgetOptions.elementAt(_selectedIndex),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                  CustomScrollView(
                    scrollBehavior:
                        ScrollConfiguration.of(context).copyWith(scrollbars: false),
                    slivers: [
                      SliverList(
                        delegate: SliverChildListDelegate(
                          [
                            Container(
                              height: 600,
                              color: Colors.blue[200],
                              child: const Center(
                                child: Text('Calendar Tab',
                                    style: TextStyle(fontSize: 40)),
                              ),
                            ),
                            Container(
                              height: 1200,
                              color: Colors.pink,
                              child: Center(
                                child: _widgetOptions.elementAt(_selectedIndex),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                  CustomScrollView(
                    scrollBehavior:
                        ScrollConfiguration.of(context).copyWith(scrollbars: false),
                    slivers: [
                      SliverList(
                        delegate: SliverChildListDelegate(
                          [
                            Container(
                              height: 600,
                              color: Colors.blue[200],
                              child: const Center(
                                child: Text('About Us Tab',
                                    style: TextStyle(fontSize: 40)),
                              ),
                            ),
                            Container(
                              height: 1200,
                              color: Colors.pink,
                              child: Center(
                                child: _widgetOptions.elementAt(_selectedIndex),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          bottomNavigationBar: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                label: 'Home',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.business),
                label: 'Business',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.school),
                label: 'School',
              ),
            ],
            currentIndex: _selectedIndex,
            selectedItemColor: Colors.amber[800],
            onTap: _onItemTapped,
          ),
        );
      }
    }