diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 6aa1544ccf..0adcff2ba5 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -3731,7 +3731,18 @@ class StatefulElement extends ComponentElement { assert(_state._debugLifecycleState == _StateLifecycle.created); try { _debugSetAllowIgnoredCallsToMarkNeedsBuild(true); - _state.initState(); + final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic; + assert(() { + if (debugCheckForReturnedFuture is Future) { + throw new FlutterError( + '${_state.runtimeType}.initState() returned a Future.\n' + 'State.initState() must be a void method without an `async` keyword.\n' + 'Rather than awaiting on asynchronous work directly inside of initState,\n' + 'call a separate method to do this work without awaiting it.' + ); + } + return true; + }()); } finally { _debugSetAllowIgnoredCallsToMarkNeedsBuild(false); } @@ -3753,7 +3764,18 @@ class StatefulElement extends ComponentElement { _state._widget = widget; try { _debugSetAllowIgnoredCallsToMarkNeedsBuild(true); - _state.didUpdateWidget(oldWidget); + final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic; + assert(() { + if (debugCheckForReturnedFuture is Future) { + throw new FlutterError( + '${_state.runtimeType}.didUpdateWidget() returned a Future.\n' + 'State.didUpdateWidget() must be a void method without an `async` keyword.\n' + 'Rather than awaiting on asynchronous work directly inside of didUpdateWidget,\n' + 'call a separate method to do this work without awaiting it.' + ); + } + return true; + }()); } finally { _debugSetAllowIgnoredCallsToMarkNeedsBuild(false); } diff --git a/packages/flutter/test/widgets/async_lifecycle_test.dart b/packages/flutter/test/widgets/async_lifecycle_test.dart new file mode 100644 index 0000000000..29ab937519 --- /dev/null +++ b/packages/flutter/test/widgets/async_lifecycle_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class InvalidOnInitLifecycleWidget extends StatefulWidget { + const InvalidOnInitLifecycleWidget({Key key}) : super(key: key); + + @override + InvalidOnInitLifecycleWidgetState createState() => new InvalidOnInitLifecycleWidgetState(); +} + +class InvalidOnInitLifecycleWidgetState extends State { + @override + void initState() async { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return new Container(); + } +} + +class InvalidDidUpdateWidgetLifecycleWidget extends StatefulWidget { + const InvalidDidUpdateWidgetLifecycleWidget({Key key, this.id}) : super(key: key); + + final int id; + + @override + InvalidDidUpdateWidgetLifecycleWidgetState createState() => new InvalidDidUpdateWidgetLifecycleWidgetState(); +} + +class InvalidDidUpdateWidgetLifecycleWidgetState extends State { + @override + void didUpdateWidget(InvalidDidUpdateWidgetLifecycleWidget oldWidget) async { + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return new Container(); + } +} + +void main() { + testWidgets('async onInit throws FlutterError', (WidgetTester tester) async { + await tester.pumpWidget(const InvalidOnInitLifecycleWidget()); + + expect(tester.takeException(), isFlutterError); + }); + + testWidgets('async didUpdateWidget throws FlutterError', (WidgetTester tester) async { + await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(id: 1)); + await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(id: 2)); + + expect(tester.takeException(), isFlutterError); + }); +}