From 234855ca3d7d0f21df4c62a28fd9227252abd60c Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Tue, 11 Dec 2018 14:22:27 -0800 Subject: [PATCH] Handle a TabBarView special case: last tab deleted before animation ends (#24892) --- packages/flutter/lib/src/material/tabs.dart | 5 +- packages/flutter/test/material/tabs_test.dart | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index fc032459e4..fc72709e08 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1168,9 +1168,10 @@ class _TabBarViewState extends State { if (notification is ScrollUpdateNotification && !_controller.indexIsChanging) { if ((_pageController.page - _controller.index).abs() > 1.0) { _controller.index = _pageController.page.floor(); - _currentIndex=_controller.index; + _currentIndex = _controller.index; } - _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0); + if (_controller.length > 1) + _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0); } else if (notification is ScrollEndNotification) { _controller.index = _pageController.page.round(); _currentIndex = _controller.index; diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 52abbd1043..6dd33a17db 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -1895,4 +1895,55 @@ void main() { expect(find.text(AlwaysKeepAliveWidget.text, skipOffstage: false), findsOneWidget); expect(find.text('4'), findsOneWidget); }); + + testWidgets('Removing the last tab', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/24424 + + final List tabs = [const Tab(text: 'LEFT'), const Tab(text: 'RIGHT')]; + + Widget buildFrame(TabController controller) { + return boilerplate( + child: Container( + alignment: Alignment.topLeft, + child: Column( + children: [ + TabBar(controller: controller, tabs: tabs), + Expanded(child: TabBarView(controller: controller, children: tabs)), + ] + ), + ), + ); + } + + final TabController controller1 = TabController( + vsync: const TestVSync(), + length: 2, + initialIndex: 0, + ); + + await tester.pumpWidget(buildFrame(controller1)); + expect(controller1.index, 0); + + await tester.tap(find.text('RIGHT')); + expect(controller1.index, 1); + expect(controller1.indexIsChanging, true); + + // At this point the change selected tab animation hasn't completed. + // When the controller is replaced the TabBarView will see one last + // scroll notification. Since there's only one tab left, it can be + // ignored. + + final TabController controller2 = TabController( + vsync: const TestVSync(), + length: 1, + initialIndex: 0, + ); + + tabs.removeAt(1); + await tester.pumpWidget(buildFrame(controller2)); + await tester.pumpAndSettle(); + expect(controller1.indexIsChanging, false); + expect(controller2.index, 0); + + }); }