Commit d0e72d61 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Prevent looking up inherited widget values from initState() (#5609)

parent fa8dc5f8
......@@ -121,9 +121,6 @@ class _PointDemoState extends State<_PointDemo> {
@override
void initState() {
super.initState();
final Size screenSize = MediaQuery.of(context).size;
_begin = new Point(screenSize.width * 0.5, screenSize.height * 0.2);
_end = new Point(screenSize.width * 0.1, screenSize.height * 0.4);
_animation = new CurvedAnimation(parent: config.controller, curve: Curves.ease);
}
......@@ -179,6 +176,10 @@ class _PointDemoState extends State<_PointDemo> {
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
_begin = new Point(screenSize.width * 0.5, screenSize.height * 0.2);
_end = new Point(screenSize.width * 0.1, screenSize.height * 0.4);
final MaterialPointArcTween arc = new MaterialPointArcTween(begin: _begin, end: _end);
return new RawGestureDetector(
behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque,
......@@ -281,15 +282,6 @@ class _RectangleDemoState extends State<_RectangleDemo> {
@override
void initState() {
super.initState();
final Size screenSize = MediaQuery.of(context).size;
_begin = new Rect.fromLTWH(
screenSize.width * 0.5, screenSize.height * 0.2,
screenSize.width * 0.4, screenSize.height * 0.2
);
_end = new Rect.fromLTWH(
screenSize.width * 0.1, screenSize.height * 0.4,
screenSize.width * 0.3, screenSize.height * 0.3
);
_animation = new CurvedAnimation(parent: config.controller, curve: Curves.ease);
}
......@@ -344,6 +336,16 @@ class _RectangleDemoState extends State<_RectangleDemo> {
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
_begin = new Rect.fromLTWH(
screenSize.width * 0.5, screenSize.height * 0.2,
screenSize.width * 0.4, screenSize.height * 0.2
);
_end = new Rect.fromLTWH(
screenSize.width * 0.1, screenSize.height * 0.4,
screenSize.width * 0.3, screenSize.height * 0.3
);
final MaterialRectArcTween arc = new MaterialRectArcTween(begin: _begin, end: _end);
return new RawGestureDetector(
behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque,
......
......@@ -157,25 +157,12 @@ class RefreshIndicatorState extends State<RefreshIndicator> {
_sizeFactor = new Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit).animate(_sizeController);
_scaleFactor = new Tween<double>(begin: 1.0, end: 0.0).animate(_scaleController);
final ThemeData theme = Theme.of(context);
// The "value" of the circular progress indicator during a drag.
_value = new Tween<double>(
begin: 0.0,
end: 0.75
)
.animate(_sizeController);
// Fully opaque when we've reached config.displacement.
_valueColor = new ColorTween(
begin: (config.color ?? theme.accentColor).withOpacity(0.0),
end: (config.color ?? theme.accentColor).withOpacity(1.0)
)
.animate(new CurvedAnimation(
parent: _sizeController,
curve: new Interval(0.0, 1.0 / _kDragSizeFactorLimit)
));
}
@override
......@@ -346,8 +333,20 @@ class RefreshIndicatorState extends State<RefreshIndicator> {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool showIndeterminateIndicator =
_mode == _RefreshIndicatorMode.refresh || _mode == _RefreshIndicatorMode.dismiss;
// Fully opaque when we've reached config.displacement.
_valueColor = new ColorTween(
begin: (config.color ?? theme.accentColor).withOpacity(0.0),
end: (config.color ?? theme.accentColor).withOpacity(1.0)
)
.animate(new CurvedAnimation(
parent: _sizeController,
curve: new Interval(0.0, 1.0 / _kDragSizeFactorLimit)
));
return new Listener(
onPointerDown: _handlePointerDown,
onPointerMove: _handlePointerMove,
......
......@@ -768,19 +768,20 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
bool _valueIsChanging = false;
int _lastSelectedIndex = -1;
void _initSelection(TabBarSelectionState<T> selection) {
void _initSelection(TabBarSelectionState<T> newSelection) {
if (_selection == newSelection)
return;
_selection?.unregisterAnimationListener(this);
_selection = selection;
_selection = newSelection;
_selection?.registerAnimationListener(this);
if (_selection != null)
_lastSelectedIndex = _selection.index;
}
@override
void initState() {
super.initState();
scrollBehavior.isScrollable = config.isScrollable;
_initSelection(TabBarSelection.of(context));
if (_selection != null)
_lastSelectedIndex = _selection.index;
}
@override
......@@ -990,8 +991,7 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
@override
Widget buildContent(BuildContext context) {
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
if (_selection != newSelection)
_initSelection(newSelection);
_initSelection(newSelection);
assert(config.labels.isNotEmpty);
assert(Material.of(context) != null);
......@@ -1097,18 +1097,14 @@ class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements Ta
@override
TargetPlatform get platform => Theme.of(context).platform;
void _initSelection(TabBarSelectionState<T> selection) {
_selection = selection;
if (_selection != null) {
_selection.registerAnimationListener(this);
void _initSelection(TabBarSelectionState<T> newSelection) {
if (_selection == newSelection)
return;
_selection?.unregisterAnimationListener(this);
_selection = newSelection;
_selection?.registerAnimationListener(this);
if (_selection != null)
_updateItemsAndScrollBehavior();
}
}
@override
void initState() {
super.initState();
_initSelection(TabBarSelection.of(context));
}
@override
......@@ -1243,8 +1239,7 @@ class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements Ta
@override
Widget buildContent(BuildContext context) {
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
if (_selection != newSelection)
_initSelection(newSelection);
_initSelection(newSelection);
return new PageViewport(
itemsWrap: config.itemsWrap,
mainAxis: config.scrollDirection,
......
......@@ -2183,6 +2183,24 @@ class StatefulElement extends ComponentElement {
_state = null;
}
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
assert(() {
if (state._debugLifecycleState == _StateLifecycle.ready)
return true;
throw new FlutterError(
"inheritFromWidgetOfExactType($targetType) was called before ${_state.runtimeType}.initState() completed.\n"
"When an inherited widget changes, for example if the value of Theme.of() changes, "
"its dependent widgets are rebuilt. If the dependent widget's reference to "
"the inherited widget is in an constructor or an initState() method, "
"then the rebuilt dependent widget will not reflect the changes in the "
"inherited widget.\n"
"Typically references to to inherited widgets should occur in widget build() methods.\n"
);
});
return super.inheritFromWidgetOfExactType(targetType);
}
@override
void dependenciesChanged() {
super.dependenciesChanged();
......
......@@ -187,12 +187,6 @@ class _ImageState extends State<Image> {
ImageStream _imageStream;
ImageInfo _imageInfo;
@override
void initState() {
super.initState();
_resolveImage();
}
@override
void didUpdateConfig(Image oldConfig) {
if (config.image != oldConfig.image)
......@@ -240,6 +234,15 @@ class _ImageState extends State<Image> {
@override
Widget build(BuildContext context) {
// This one-time initialization could have been done in initState() since
// changes to the inherited widgets that _resolveImage depends on, notably
// DefaultAssetBundle, are handle by the dependenciesChanged() method. We're
// doing it here instead to avoid the assert that disallows references to
// inherited widgets at initState() time. We've found that assert to be a
// reliable source of real bugs, and that it is worth this minor inconvenience.
if (_imageStream == null)
_resolveImage();
return new RawImage(
image: _imageInfo?.image,
width: config.width,
......
......@@ -255,12 +255,6 @@ abstract class PageableState<T extends Pageable> extends ScrollableState<T> {
return null;
}
@override
void initState() {
super.initState();
_updateScrollBehavior();
}
@override
void didUpdateConfig(Pageable oldConfig) {
super.didUpdateConfig(oldConfig);
......
......@@ -29,6 +29,29 @@ class ValueInherited extends InheritedWidget {
bool updateShouldNotify(ValueInherited oldWidget) => value != oldWidget.value;
}
class ExpectFail extends StatefulWidget {
ExpectFail(this.onError);
final VoidCallback onError;
@override
ExpectFailState createState() => new ExpectFailState();
}
class ExpectFailState extends State<ExpectFail> {
@override
void initState() {
super.initState();
try {
context.inheritFromWidgetOfExactType(TestInherited); // should fail
} catch (e) {
config.onError();
}
}
@override
Widget build(BuildContext context) => new Container();
}
void main() {
testWidgets('Inherited notifies dependents', (WidgetTester tester) async {
List<TestInherited> log = <TestInherited>[];
......@@ -437,4 +460,16 @@ void main() {
);
expect(buildCount, equals(2));
});
testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/5491
bool exceptionCaught = false;
TestInherited parent = new TestInherited(child: new ExpectFail(() {
exceptionCaught = true;
}));
await tester.pumpWidget(parent);
expect(exceptionCaught, isTrue);
});
}
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