Unverified Commit 29f7a8e8 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Simplify mark needs build (#108383)

parent 6f4db37e
......@@ -2595,7 +2595,6 @@ class BuildOwner {
assert(_debugStateLocked);
Element? debugPreviousBuildTarget;
assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = context;
return true;
......@@ -2605,7 +2604,6 @@ class BuildOwner {
callback();
} finally {
assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
assert(_debugCurrentBuildTarget == context);
_debugCurrentBuildTarget = debugPreviousBuildTarget;
_debugElementWasRebuilt(context);
......@@ -4497,17 +4495,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
// Whether we've already built or not. Set in [rebuild].
bool _debugBuiltOnce = false;
// We let widget authors call setState from initState, didUpdateWidget, and
// build even when state is locked because its convenient and a no-op anyway.
// This flag ensures that this convenience is only allowed on the element
// currently undergoing initState, didUpdateWidget, or build.
bool _debugAllowIgnoredCallsToMarkNeedsBuild = false;
bool _debugSetAllowIgnoredCallsToMarkNeedsBuild(bool value) {
assert(_debugAllowIgnoredCallsToMarkNeedsBuild == !value);
_debugAllowIgnoredCallsToMarkNeedsBuild = value;
return true;
}
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
......@@ -4529,28 +4516,24 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
return true;
}
if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
final List<DiagnosticsNode> information = <DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called during build.'),
ErrorDescription(
'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
'is already in the process of building widgets. A widget can be marked as '
'needing to be built during the build phase only if one of its ancestors '
'is currently building. This exception is allowed because the framework '
'builds parent widgets before children, which means a dirty descendant '
'will always be built. Otherwise, the framework might not visit this '
'widget during this build phase.',
),
describeElement('The widget on which setState() or markNeedsBuild() was called was'),
];
if (owner!._debugCurrentBuildTarget != null) {
information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
}
throw FlutterError.fromParts(information);
final List<DiagnosticsNode> information = <DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called during build.'),
ErrorDescription(
'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
'is already in the process of building widgets. A widget can be marked as '
'needing to be built during the build phase only if one of its ancestors '
'is currently building. This exception is allowed because the framework '
'builds parent widgets before children, which means a dirty descendant '
'will always be built. Otherwise, the framework might not visit this '
'widget during this build phase.',
),
describeElement('The widget on which setState() or markNeedsBuild() was called was'),
];
if (owner!._debugCurrentBuildTarget != null) {
information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
}
assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
throw FlutterError.fromParts(information);
} else if (owner!._debugStateLocked) {
assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
ErrorDescription(
......@@ -4868,7 +4851,6 @@ abstract class ComponentElement extends Element {
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget? built;
try {
assert(() {
......@@ -4898,7 +4880,6 @@ abstract class ComponentElement extends Element {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
......@@ -5010,25 +4991,20 @@ class StatefulElement extends ComponentElement {
@override
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
assert(() {
state._debugLifecycleState = _StateLifecycle.initialized;
return true;
......@@ -5060,25 +5036,20 @@ class StatefulElement extends ComponentElement {
// asserts.
_dirty = true;
state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
rebuild();
}
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('setState can be called from build, initState, didChangeDependencies, and didUpdateWidget', (WidgetTester tester) async {
// Initial build.
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: TestWidget(value: 1),
),
);
final _TestWidgetState state = tester.state(find.byType(TestWidget));
expect(state.calledDuringBuild, 1);
expect(state.calledDuringInitState, 1);
expect(state.calledDuringDidChangeDependencies, 1);
expect(state.calledDuringDidUpdateWidget, 0);
// Update Widget.
late Widget child;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: child = const TestWidget(value: 2),
),
);
expect(state.calledDuringBuild, 2); // Increased.
expect(state.calledDuringInitState, 1);
expect(state.calledDuringDidChangeDependencies, 1);
expect(state.calledDuringDidUpdateWidget, 1); // Increased.
// Build after state is dirty.
state.markNeedsBuild();
await tester.pump();
expect(state.calledDuringBuild, 3); // Increased.
expect(state.calledDuringInitState, 1);
expect(state.calledDuringDidChangeDependencies, 1);
expect(state.calledDuringDidUpdateWidget, 1);
// Change dependency.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl, // Changed.
child: child,
),
);
expect(state.calledDuringBuild, 4); // Increased.
expect(state.calledDuringInitState, 1);
expect(state.calledDuringDidChangeDependencies, 2); // Increased.
expect(state.calledDuringDidUpdateWidget, 1);
});
}
class TestWidget extends StatefulWidget {
const TestWidget({super.key, required this.value});
final int value;
@override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
int calledDuringBuild = 0;
int calledDuringInitState = 0;
int calledDuringDidChangeDependencies = 0;
int calledDuringDidUpdateWidget = 0;
void markNeedsBuild() {
setState(() {
// Intentionally left empty.
});
}
@override
void initState() {
super.initState();
setState(() {
calledDuringInitState++;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
setState(() {
calledDuringDidChangeDependencies++;
});
}
@override
void didUpdateWidget(TestWidget oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() {
calledDuringDidUpdateWidget++;
});
}
@override
Widget build(BuildContext context) {
setState(() {
calledDuringBuild++;
});
return SizedBox.expand(
child: Text(Directionality.of(context).toString()),
);
}
}
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