Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RefreshIndicator syncs scrollposition across tabs when using TabbarView with AutomaticKeepAliveClientMixin inside NestedScrollView #62833

Closed
janjandev opened this issue Aug 4, 2020 · 25 comments
Labels
f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. found in release: 3.3 Found to occur in 3.3 found in release: 3.7 Found to occur in 3.7 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list

Comments

@janjandev
Copy link

Use doc sample code for NestedScrollView , but have many problem.

  • If you don't set SliderAppBar's expandedHeight, listview inside tabbarview can't scroll vertical.
  • so I comment SliverOverlapAbsorber and SliverOverlapInjector code and can scroll vertical,
  • but for retain each page inside tabbarview, I use AutomaticKeepAliveClientMixin and wantKeepAlive set to true,
  • 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.
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() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> _tabs = [
    "AAA",
    "BBB",
    "CCC",
    "DDD",
    "EEE",
    "FFF",
  ];

  @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 "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,
                floating: false,
                snap: false,
                // 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(
                  isScrollable: true,
                  // 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 RefreshIndicator(child: 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(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: () => Future.delayed(Duration(seconds: 5)));
                },
              ),
            );
          }).toList(),
        ),
      ),
    );

  }
}

class TabPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _TabPageState();
  }

}

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => 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,
      ),
    );
  }
}

```shell
D:\flutter-projects\my_flutter_app>flutter doctor -v
[√] Flutter (Channel stable, v1.17.5, on Microsoft Windows [Version 10.0.18363.959], locale zh-CN)
    • Flutter version 1.17.5 at D:\flutter
    • Framework revision 8af6b2f038 (5 weeks ago), 2020-06-30 12:53:55 -0700Engine revision ee76268252
    • Dart version 2.8.4


[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at D:\Android\SdkPlatform android-29, build-tools 29.0.3ANDROID_HOME = D:\Android\SdkJava binary at: D:\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    • All Android licenses accepted.

[√] Android Studio (version 4.0)
    • Android Studio at D:\Android\Android StudioFlutter plugin version 48.0.2Dart plugin version 193.7361Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)

[√] Connected device (1 available)
    • SM G973U1R38M202PE2A • android-arm64 • Android 10 (API 29)

• No issues found!

D:\flutter-projects\my_flutter_app>
@z759341385
Copy link

z759341385 commented Aug 4, 2020

@janjandev
Copy link
Author

you can try to use this packge
https://pub.dev/packages/extended_nested_scroll_view#extendednestedscrollview

Unfortunately, it seems does not solve these problems, am I right ?

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() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  final List<String> _tabs = [
    "AAA",
    "BBB",
    "CCC",
    "DDD",
    "EEE",
    "FFF",
  ];

  TabController tcl;

  @override
  void initState() {
    super.initState();
    tcl = TabController(length: _tabs.length, vsync: this);
  }

  @override
  void dispose() {
    tcl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final double statusBarHeight = MediaQuery.of(context).padding.top;
    final double pinnedHeaderHeight = statusBarHeight + kToolbarHeight;

    return extended.NestedScrollView(
      pinnedHeaderSliverHeightBuilder: () {
        return pinnedHeaderHeight;
      },
      innerScrollPositionKeyBuilder: () => Key('${_tabs[tcl.index]}'),
        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,
                floating: true,
                snap: true,
                // 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(
                  controller: tcl,
                  isScrollable: true,
                  // These are the widgets to put in each tab in the tab bar.
                  tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                ),
              ),
//            ),
          ];
        },
        body: TabBarView(
          controller: tcl,
          // 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 RefreshIndicator(child: 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(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(key: Key(name)),
                      ),
                    ],
                  ), onRefresh: () => Future.delayed(Duration(seconds: 5)));
                },
              ),
            );
          }).toList(),
        ),
      );
  }
}

class TabPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _TabPageState();
  }

  TabPage({Key key}) : super(key: key);
}

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return extended.NestedScrollViewInnerScrollPositionKeyWidget(
        widget.key,
        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,
          ),
        ));
  }
}

@TahaTesser TahaTesser changed the title NestedScrollView + TabBarView + RefreshIndicator bug RefreshIndicator has issues when using TabbarView with AutomaticKeepAliveClientMixin inside NestedScrollView Aug 4, 2020
@TahaTesser
Copy link
Member

Code Sample
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> _tabs = [
    "AAA",
    "BBB",
    "CCC",
    "DDD",
    "EEE",
    "FFF",
  ];

  @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 "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,
              floating: false,
              snap: false,
              // 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(
                isScrollable: true,
                // 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 RefreshIndicator(
                      child: 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(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: () => Future.delayed(Duration(seconds: 5)));
                },
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

class TabPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _TabPageState();
  }
}

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => 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,
      ),
    );
  }
}

flutter doctor -v
[✓] Flutter (Channel master, 1.21.0-6.0.pre.160, on Mac OS X 10.15.6 19G73,
    locale en-GB)
    • Flutter version 1.21.0-6.0.pre.160 at /Users/taha/Code/flutter_master
    • Framework revision 31ee51a302 (10 hours ago), 2020-08-03 17:27:21 -0700
    • Engine revision fa16c3d0ba
    • Dart version 2.10.0 (build 2.10.0-2.0.dev 0f0e04ec3a)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
    • Android SDK at /Users/taha/Code/sdk
    • Platform android-30, build-tools 30.0.1
    • ANDROID_HOME = /Users/taha/Code/sdk
    • Java binary at: /Applications/Android
      Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.6)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.6, Build version 11E708
    • CocoaPods version 1.9.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.0)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 48.0.2
    • Dart plugin version 193.7361
    • Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6222593)

[✓] VS Code (version 1.47.3)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.13.1

 
[✓] Connected device (5 available)            
    • Taha’s iPhone (mobile) • 00008020-001059882212002E            • ios            • iOS 13.6
    • iPhone 11 (mobile)     • 1EB615AF-5AB9-4AAA-A587-A6642BB79484 • ios            •
      com.apple.CoreSimulator.SimRuntime.iOS-13-6 (simulator)
    • macOS (desktop)        • macos                                • darwin-x64     • Mac OS X
      10.15.6 19G73
    • Web Server (web)       • web-server                           • web-javascript • Flutter Tools
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome
      84.0.4147.105

• No issues found!

@TahaTesser TahaTesser added f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. found in release: 1.21 Found to occur in 1.21 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on labels Aug 4, 2020
@ChihchengHsieh
Copy link

Any solution or workaround at this moment?

@gabrielCuringa
Copy link

There is this post on StackOverflow which is two years old and doesn't seem to fix the problem... https://stackoverflow.com/questions/52264270/refreshindicator-with-nestedscrollview

@ingmferrer
Copy link

Bump this

@lanrehnics
Copy link

Any fix for this ?

@azazadev
Copy link

any update ?

@TomJoe2021
Copy link

anyone?

@ondev
Copy link

ondev commented May 25, 2021

Have the same issue. fixed already?

@martipello
Copy link

but for retain each page inside tabbarview, I use AutomaticKeepAliveClientMixin and wantKeepAlive set to true,
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.

this is my problem been debugging checking hash codes are all unique its definitely a problem with using tabs auto keep alive and refresh indicator

@Ruoyu2021
Copy link

很困扰,希望可以尽快修复或者给出解决方案。

@PrakashKumar0143
Copy link

Hi,
I am also facing the same issue, so is there any solution or what?
"Don't use one refreshController to multiple SmartRefresher,It will cause some unexpected bugs mostly in TabBarView"
How to reproduce?
You can select the tabs froom right to left, it will work perfect.
But when again you will try to come form right to left in reverse 1 by 1, then this issue is happening.
Any solution?

@tamzidpeace
Copy link

tamzidpeace commented Dec 1, 2021

Initialized RefreshController & onRefresh function inside the Widget build(Context context)... . like this

`// this is my project code

@OverRide
Widget build(BuildContext context) {

final RefreshController homeRefreshController = RefreshController();

void onRefresh() async {
  try {
    await _homeController.getDashbaordInfo();
    homeRefreshController.refreshCompleted();
  } catch (e) {
    homeRefreshController.refreshFailed();
  }
}`

...

It works for me.

@ASHFAQPATWARI
Copy link

Facing exactly same issue. All the refreshindicators are triggered together

@icnahom
Copy link

icnahom commented Dec 8, 2021

Hello @tamzidpeace what's a RefreshController? Were you facing the same issue, does pulling a refresh on one of the children of TabBarView trigger a refresh on all the other children?

@tamzidpeace
Copy link

In smart refresh, you need a RefreshController.

@SatishKumarRaizada
Copy link

SatishKumarRaizada commented Apr 5, 2022

Any update on the solution or work around. I have exactly same issue as it is mentioned in the problem title above "RefreshIndicator has issues when using TabbarView with AutomaticKeepAliveClientMixin inside NestedScrollView".

It calls all the refresh indicator at once no matter from which page you are refreshing.

@Piinks Piinks added the P2 Important issues not at the top of the work list label Jun 16, 2022
@kishan-dhankecha
Copy link
Contributor

Any Update?

1 similar comment
@icodeyou
Copy link

Any Update?

@Khurshidddbek
Copy link

Developers we need an update.

@Ra3wej
Copy link

Ra3wej commented Jan 2, 2023

setting

primary=false
of the tab that has the refresh indicator fixed the problem for me.
maybe you can set all the tab`s primary attribute that you have to false ?
image

@maheshmnj
Copy link
Member

Reproducible on stable 3.3 and master 3.7

  • Scroll position is synced with the rest of the tabs
  • Refreshindicator refreshes all tabs
code sample
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> _tabs = [
    "AAA",
    "BBB",
    "CCC",
    "DDD",
    "EEE",
    "FFF",
  ];

  @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 "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'),
              pinned: true,
              floating: false,
              snap: false,
              // 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(
                isScrollable: true,
                // These are the widgets to put in each tab in the tab bar.
                tabs: _tabs.map((String name) => Tab(text: name)).toList(),
              ),
            ),
//            ),
          ];
        },
        body: TabBarView(
          children: _tabs.map((String name) {
            return SafeArea(
              top: false,
              bottom: false,
              child: Builder(
                builder: (BuildContext context) {
                  return RefreshIndicator(
                      child: CustomScrollView(
                        key: PageStorageKey<String>(name),
                        slivers: const <Widget>[
//                      SliverOverlapInjector(
//                        // This is the flip side of the SliverOverlapAbsorber
//                        // above.
//                        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
//                      ),
                          SliverPadding(
                            padding: EdgeInsets.all(0),
                            sliver: TabPage(),
                          ),
                        ],
                      ),
                      onRefresh: () =>
                          Future.delayed(const Duration(seconds: 5)));
                },
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

class TabPage extends StatefulWidget {
  const TabPage({super.key});

  @override
  State<StatefulWidget> createState() {
    return _TabPageState();
  }
}

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return SliverFixedExtentList(
      itemExtent: 48.0,
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return Material(
              child: ListTile(
            title: Text('Item $index'),
          ));
        },
        childCount: 30,
      ),
    );
  }
}
flutter doctor -v (mac)
[✓] Flutter (Channel master, 3.7.0-13.0.pre.131, on macOS 13.1 22C65 darwin-arm64, locale en-IN)
    • Flutter version 3.7.0-13.0.pre.131 on channel master at /Users/mahesh/Development/flutter_master
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 0196e6050b (2 days ago), 2022-12-31 11:10:23 -0500
    • Engine revision 932591ec04
    • Dart version 3.0.0 (build 3.0.0-76.0.dev)
    • DevTools version 2.20.0
    • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks
      and upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4)
    • Android SDK at /Users/mahesh/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0-rc4
    • ANDROID_HOME = /Users/mahesh/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14A400
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] IntelliJ IDEA Community Edition (version 2021.2.1)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin version 61.2.4
    • Dart plugin version 212.5080.8

[✓] VS Code (version 1.74.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.56.0

[✓] Connected device (3 available)
    • iPhone 12 Pro (mobile) • 026D5789-9E78-4AD5-B1B2-3F8D4E7F65E4 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-5 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 13.1 22C65 darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 108.0.5359.124

[✓] HTTP Host Availability
    • All required HTTP hosts are available
    
• No issues found!
[✓] Flutter (Channel stable, 3.3.9, on macOS 12.6 21G115 darwin-arm, locale en-IN)
    • Flutter version 3.3.9 on channel stable at /Users/mahesh/Development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b8f7f1f986 (24 hours ago), 2022-11-23 06:43:51 +0900
    • Engine revision 8f2221fbef
    • Dart version 2.18.5
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4)
    • Android SDK at /Users/mahesh/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0-rc4
    • ANDROID_HOME = /Users/mahesh/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14A400
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] IntelliJ IDEA Community Edition (version 2021.2.1)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin version 61.2.4
    • Dart plugin version 212.5080.8

[✓] VS Code (version 1.70.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.53.20221101

[✓] Connected device (3 available)
    • iPhone 12 Pro (mobile) • 026D5789-9E78-4AD5-B1B2-3F8D4E7F65E4 • ios            •
      com.apple.CoreSimulator.SimRuntime.iOS-14-5 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 12.6 21G115 darwin-arm
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 107.0.5304.110

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

@maheshmnj maheshmnj added found in release: 3.3 Found to occur in 3.3 found in release: 3.7 Found to occur in 3.7 and removed found in release: 1.21 Found to occur in 1.21 labels Jan 10, 2023
@maheshmnj maheshmnj changed the title RefreshIndicator has issues when using TabbarView with AutomaticKeepAliveClientMixin inside NestedScrollView RefreshIndicator syncs scrollposition across tabs when using TabbarView with AutomaticKeepAliveClientMixin inside NestedScrollView Jan 10, 2023
@Piinks
Copy link
Contributor

Piinks commented May 30, 2023

Referring to OP here:

If you don't set SliderAppBar's expandedHeight, listview inside tabbarview can't scroll vertical.

This should be fixed by #127718

every page have a RefreshIndicator , when swipe 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.

The root of this issue is #40740, so I am going to close this issue to refer there for updates, we try to consolidate dupes to focus everything in one issue. Thanks for reporting and providing updates! Let's follow up over there. :)

@Piinks Piinks closed this as completed May 30, 2023
auto-submit bot pushed a commit that referenced this issue Jun 5, 2023
#### (plus some more docs)

Fixes #76760
Fixes #82391
Fixes #45619
Fixes #117316
Fixes #110956
Fixes #127282 
Fixes #32563
Fixes #46089 
Fixes #79077
Part of fixing #62833

This fixes (a bunch of) issues that have been reported differently over the years, but all have the same root cause. Sometimes the NestedScrollView would incorrectly calculate whether or not there is enough content to allow scrolling. This would only apply to drag scrolling (not mouse wheel scrolling for example). This did not relate to how the extent of the NestedScrollView is computed, but just the logic that enabled the actual drag gestures. This fixes that. :)
@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. found in release: 3.3 Found to occur in 3.3 found in release: 3.7 Found to occur in 3.7 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list
Projects
None yet
Development

No branches or pull requests