Unverified Commit 9e4ab00e authored by xster's avatar xster Committed by GitHub

Handle CupertinoTabScaffold rebuilds with deleted tabs (#27576)

parent 4a9e5bc1
...@@ -54,7 +54,10 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -54,7 +54,10 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
), ),
), ),
}) : assert(items != null), }) : assert(items != null),
assert(items.length >= 2), assert(
items.length >= 2,
"Tabs need at least 2 items to conform to Apple's HIG",
),
assert(currentIndex != null), assert(currentIndex != null),
assert(0 <= currentIndex && currentIndex < items.length), assert(0 <= currentIndex && currentIndex < items.length),
assert(iconSize != null), assert(iconSize != null),
...@@ -75,7 +78,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -75,7 +78,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
/// The index into [items] of the current active item. /// The index into [items] of the current active item.
/// ///
/// Must not be null. /// Must not be null and must inclusively be between 0 and the number of tabs
/// minus 1.
final int currentIndex; final int currentIndex;
/// The background color of the tab bar. If it contains transparency, the /// The background color of the tab bar. If it contains transparency, the
......
...@@ -167,6 +167,16 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -167,6 +167,16 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
@override @override
void didUpdateWidget(CupertinoTabScaffold oldWidget) { void didUpdateWidget(CupertinoTabScaffold oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (_currentPage >= widget.tabBar.items.length) {
// Clip down to an acceptable range.
_currentPage = widget.tabBar.items.length - 1;
// Sanity check, since CupertinoTabBar.items's minimum length is 2.
assert(
_currentPage >= 0,
'CupertinoTabBar is expected to keep at least 2 tabs after updating',
);
}
// The user can still specify an exact desired index.
if (widget.tabBar.currentIndex != oldWidget.tabBar.currentIndex) { if (widget.tabBar.currentIndex != oldWidget.tabBar.currentIndex) {
_currentPage = widget.tabBar.currentIndex; _currentPage = widget.tabBar.currentIndex;
} }
......
...@@ -395,6 +395,82 @@ void main() { ...@@ -395,6 +395,82 @@ void main() {
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400)); expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400));
expect(MediaQuery.of(innerContext).padding.bottom, 0); expect(MediaQuery.of(innerContext).padding.bottom, 0);
}); });
testWidgets('Deleting tabs after selecting them works', (WidgetTester tester) async {
final List<int> tabsBuilt = <int>[];
BottomNavigationBarItem tabGenerator(int index) {
return BottomNavigationBarItem(
icon: const ImageIcon(TestImageProvider(24, 24)),
title: Text('Tab ${index + 1}'),
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: List<BottomNavigationBarItem>.generate(4, tabGenerator),
onTap: (int newTab) => selectedTabs.add(newTab),
),
tabBuilder: (BuildContext context, int index) {
tabsBuilt.add(index);
return Text('Page ${index + 1}');
},
),
),
);
expect(tabsBuilt, <int>[0]);
// selectedTabs list is appended to on onTap callbacks. We didn't tap
// any tabs yet.
expect(selectedTabs, <int>[]);
tabsBuilt.clear();
await tester.tap(find.text('Tab 4'));
await tester.pump();
// Tabs 1 and 4 are built but only one is onstage.
expect(tabsBuilt, <int>[0, 3]);
expect(selectedTabs, <int>[3]);
expect(find.text('Page 1', skipOffstage: false), isOffstage);
expect(find.text('Page 4'), findsOneWidget);
tabsBuilt.clear();
// Delete 2 tabs.
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: List<BottomNavigationBarItem>.generate(2, tabGenerator),
onTap: (int newTab) => selectedTabs.add(newTab),
),
tabBuilder: (BuildContext context, int index) {
tabsBuilt.add(index);
// Change the builder too.
return Text('Different page ${index + 1}');
},
),
),
);
expect(tabsBuilt, <int>[0, 1]);
// We didn't tap on any additional tabs to invoke the onTap callback. We
// just deleted a tab.
expect(selectedTabs, <int>[3]);
// Tab 1 was previously built so it's rebuilt again, albeit offstage.
expect(find.text('Different page 1', skipOffstage: false), isOffstage);
// Since all the tabs after tab 2 are deleted, tab 2 is now the last tab and
// the actively shown tab.
expect(find.text('Different page 2'), findsOneWidget);
// No more tab 4 since it's deleted.
expect(find.text('Different page 4', skipOffstage: false), findsNothing);
// We also changed the builder so no tabs should be built with the old
// builder.
expect(find.text('Page 1', skipOffstage: false), findsNothing);
expect(find.text('Page 2', skipOffstage: false), findsNothing);
expect(find.text('Page 4', skipOffstage: false), findsNothing);
});
} }
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) { CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment