title: Flutter NestedScrollView + TabBarView + RefreshIndicator bug
body: <h2><strong>Use doc sample code for NestedScrollView , but have many problems.</strong></h2>
  <ul>
  <li>If you don't set SliderAppBar's expandedHeight, listview inside
  tabbarview can't scroll vertical.</li>
  <li>so I comment SliverOverlapAbsorber
  and SliverOverlapInjector code and can scroll vertical,</li>
  <li>but for
  retain each page inside tabbarview, I use
  AutomaticKeepAliveClientMixin and wantKeepAlive set to true,</li>
  <li>every
  page have a RefreshIndicator , when swip to refresh one page, other
  all page trigger onRefresh and show refresh progress at the same time
  and also all page scroll position all sync to the same position.</li>
  </ul>
  <pre class="lang-dart prettyprint-override"><code>class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter Demo'),
      );
    }
  }
  
  class MyHomePage extends StatefulWidget {
    MyHomePage({Key key, this.title}) : super(key: key);
  
    final String title;
  
    @override
    _MyHomePageState createState() =&gt; _MyHomePageState();
  }
  
  class _MyHomePageState extends State&lt;MyHomePage&gt; {
    final List&lt;String&gt; _tabs = [
      &quot;AAA&quot;,
      &quot;BBB&quot;,
      &quot;CCC&quot;,
      &quot;DDD&quot;,
      &quot;EEE&quot;,
      &quot;FFF&quot;,
    ];
  
    @override
    Widget build(BuildContext context) {
      return DefaultTabController(
        length: _tabs.length, // This is the number of tabs.
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            // These are the slivers that show up in the &quot;outer&quot; scroll view.
            return &lt;Widget&gt;[
  //            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 &quot;inner&quot; 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 &quot;headerSliverBuilder&quot; 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,
                  floating: false,
                  snap: false,
                  // The &quot;forceElevated&quot; property causes the SliverAppBar to show
                  // a shadow. The &quot;innerBoxIsScrolled&quot; parameter is true when the
                  // inner scroll view is scrolled beyond its &quot;zero&quot; 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(
                    isScrollable: true,
                    // These are the widgets to put in each tab in the tab bar.
                    tabs: _tabs.map((String name) =&gt; 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
                  // &quot;inside&quot; the NestedScrollView, so that
                  // sliverOverlapAbsorberHandleFor() can find the
                  // NestedScrollView.
                  builder: (BuildContext context) {
                    return RefreshIndicator(child: CustomScrollView(
                      // The &quot;controller&quot; and &quot;primary&quot; members should be left
                      // unset, so that the NestedScrollView can control this
                      // inner scroll view.
                      // If the &quot;controller&quot; 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&lt;String&gt;(name),
                      slivers: &lt;Widget&gt;[
  //                      SliverOverlapInjector(
  //                        // This is the flip side of the SliverOverlapAbsorber
  //                        // above.
  //                        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
  //                      ),
                        SliverPadding(
                          padding: const EdgeInsets.all(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: TabPage(),
                        ),
                      ],
                    ), onRefresh: () =&gt; Future.delayed(Duration(seconds: 5)));
                  },
                ),
              );
            }).toList(),
          ),
        ),
      );
  
    }
  }
  
  class TabPage extends StatefulWidget {
    @override
    State&lt;StatefulWidget&gt; createState() {
      return _TabPageState();
    }
  
  }
  
  class _TabPageState extends State&lt;TabPage&gt; with AutomaticKeepAliveClientMixin {
    @override
    bool get wantKeepAlive =&gt; true;
  
    @override
    Widget build(BuildContext context) {
      super.build(context);
      return 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 Material(child: 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,
        ),
      );
    }
  }
  </code></pre>