Unverified Commit 2f75005a authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Fix Exception on Nested TabBarView disposal (#31581)

* Add Flag to determine if pixels is set by viewport during disposal

* Add TODO to remove nested TabBarView workaround once unnecessary build/dispose issues are resolved
parent d53115ab
......@@ -251,6 +251,29 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
final int initialPage;
double _pageToUseOnStartup;
/// If [pixels] isn't set by [applyViewportDimension] before [dispose] is
/// called, this could throw an assert as [pixels] will be set to null.
///
/// With [Tab]s, this happens when there are nested [TabBarView]s and there
/// is an attempt to warp over the nested tab to a tab adjacent to it.
///
/// This flag will be set to true once the dimensions have been established
/// and [pixels] is set.
bool isInitialPixelsValueSet = false;
@override
void dispose() {
// TODO(shihaohong): remove workaround once these issues have been
// resolved, https://github.com/flutter/flutter/issues/32054,
// https://github.com/flutter/flutter/issues/32056
// Sets `pixels` to a non-null value before `ScrollPosition.dispose` is
// invoked if it was never set by `applyViewportDimension`.
if (pixels == null && !isInitialPixelsValueSet) {
correctPixels(0);
}
super.dispose();
}
@override
double get viewportFraction => _viewportFraction;
double _viewportFraction;
......@@ -295,8 +318,10 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
final double oldPixels = pixels;
final double page = (oldPixels == null || oldViewportDimensions == 0.0) ? _pageToUseOnStartup : getPageFromPixels(oldPixels, oldViewportDimensions);
final double newPixels = getPixelsFromPage(page);
if (newPixels != oldPixels) {
correctPixels(newPixels);
isInitialPixelsValueSet = true;
return false;
}
return result;
......
......@@ -67,6 +67,42 @@ class AlwaysKeepAliveState extends State<AlwaysKeepAliveWidget>
}
}
class _NestedTabBarContainer extends StatelessWidget {
const _NestedTabBarContainer({
this.tabController,
});
final TabController tabController;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: Column(
children: <Widget>[
TabBar(
controller: tabController,
tabs: const <Tab>[
Tab(text: 'Yellow'),
Tab(text: 'Grey'),
],
),
Expanded(
flex: 1,
child: TabBarView(
controller: tabController,
children: <Widget>[
Container(color: Colors.yellow),
Container(color: Colors.grey),
],
),
)
],
),
);
}
}
Widget buildFrame({
Key tabBarKey,
List<String> tabs,
......@@ -942,6 +978,51 @@ void main() {
expect(tabController.index, 0);
});
testWidgets('Nested TabBarView sets ScrollController pixels to non-null value '
'when disposed before it is set by the applyViewportDimension', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/18756
final TabController _mainTabController = TabController(length: 4, vsync: const TestVSync());
final TabController _nestedTabController = TabController(length: 2, vsync: const TestVSync());
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Exception for Nested Tabs'),
bottom: TabBar(
controller: _mainTabController,
tabs: const <Widget>[
Tab(icon: Icon(Icons.add), text: 'A'),
Tab(icon: Icon(Icons.add), text: 'B'),
Tab(icon: Icon(Icons.add), text: 'C'),
Tab(icon: Icon(Icons.add), text: 'D'),
],
),
),
body: TabBarView(
controller: _mainTabController,
children: <Widget>[
Container(color: Colors.red),
_NestedTabBarContainer(tabController: _nestedTabController),
Container(color: Colors.green),
Container(color: Colors.indigo),
],
),
),
)
);
// expect first tab to be selected
expect(_mainTabController.index, 0);
// tap on third tab
await tester.tap(find.text('C'));
await tester.pumpAndSettle();
// expect third tab to be selected without exceptions
expect(_mainTabController.index, 2);
});
testWidgets('TabBarView scrolls end close to a new page with custom physics', (WidgetTester tester) async {
final TabController tabController = TabController(
vsync: const TestVSync(),
......
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