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 { ...@@ -2595,7 +2595,6 @@ class BuildOwner {
assert(_debugStateLocked); assert(_debugStateLocked);
Element? debugPreviousBuildTarget; Element? debugPreviousBuildTarget;
assert(() { assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
debugPreviousBuildTarget = _debugCurrentBuildTarget; debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = context; _debugCurrentBuildTarget = context;
return true; return true;
...@@ -2605,7 +2604,6 @@ class BuildOwner { ...@@ -2605,7 +2604,6 @@ class BuildOwner {
callback(); callback();
} finally { } finally {
assert(() { assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
assert(_debugCurrentBuildTarget == context); assert(_debugCurrentBuildTarget == context);
_debugCurrentBuildTarget = debugPreviousBuildTarget; _debugCurrentBuildTarget = debugPreviousBuildTarget;
_debugElementWasRebuilt(context); _debugElementWasRebuilt(context);
...@@ -4497,17 +4495,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4497,17 +4495,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
// Whether we've already built or not. Set in [rebuild]. // Whether we've already built or not. Set in [rebuild].
bool _debugBuiltOnce = false; 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 /// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame. /// rebuild in the next frame.
/// ///
...@@ -4529,7 +4516,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4529,7 +4516,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) { if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
return true; return true;
} }
if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
final List<DiagnosticsNode> information = <DiagnosticsNode>[ final List<DiagnosticsNode> information = <DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called during build.'), ErrorSummary('setState() or markNeedsBuild() called during build.'),
ErrorDescription( ErrorDescription(
...@@ -4547,10 +4533,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4547,10 +4533,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was')); information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
} }
throw FlutterError.fromParts(information); throw FlutterError.fromParts(information);
}
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)
} else if (owner!._debugStateLocked) { } else if (owner!._debugStateLocked) {
assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
throw FlutterError.fromParts(<DiagnosticsNode>[ throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'), ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
ErrorDescription( ErrorDescription(
...@@ -4868,7 +4851,6 @@ abstract class ComponentElement extends Element { ...@@ -4868,7 +4851,6 @@ abstract class ComponentElement extends Element {
@override @override
@pragma('vm:notify-debugger-on-exception') @pragma('vm:notify-debugger-on-exception')
void performRebuild() { void performRebuild() {
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget? built; Widget? built;
try { try {
assert(() { assert(() {
...@@ -4898,7 +4880,6 @@ abstract class ComponentElement extends Element { ...@@ -4898,7 +4880,6 @@ abstract class ComponentElement extends Element {
// We delay marking the element as clean until after calling build() so // We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored. // that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false; _dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
} }
try { try {
_child = updateChild(_child, built, slot); _child = updateChild(_child, built, slot);
...@@ -5010,8 +4991,6 @@ class StatefulElement extends ComponentElement { ...@@ -5010,8 +4991,6 @@ class StatefulElement extends ComponentElement {
@override @override
void _firstBuild() { void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created); assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic; final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() { assert(() {
if (debugCheckForReturnedFuture is Future) { if (debugCheckForReturnedFuture is Future) {
...@@ -5026,9 +5005,6 @@ class StatefulElement extends ComponentElement { ...@@ -5026,9 +5005,6 @@ class StatefulElement extends ComponentElement {
} }
return true; return true;
}()); }());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
assert(() { assert(() {
state._debugLifecycleState = _StateLifecycle.initialized; state._debugLifecycleState = _StateLifecycle.initialized;
return true; return true;
...@@ -5060,8 +5036,6 @@ class StatefulElement extends ComponentElement { ...@@ -5060,8 +5036,6 @@ class StatefulElement extends ComponentElement {
// asserts. // asserts.
_dirty = true; _dirty = true;
state._widget = widget as StatefulWidget; state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic; final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
assert(() { assert(() {
if (debugCheckForReturnedFuture is Future) { if (debugCheckForReturnedFuture is Future) {
...@@ -5076,9 +5050,6 @@ class StatefulElement extends ComponentElement { ...@@ -5076,9 +5050,6 @@ class StatefulElement extends ComponentElement {
} }
return true; return true;
}()); }());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild(); 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