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
This commit is contained in:
parent
d53115ab2e
commit
2f75005a16
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user