-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the "setState called during build" in common-errors.md #11353
base: main
Are you sure you want to change the base?
Changes from all commits
2d04abb
bfce89d
f347472
10d9eef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -475,43 +475,50 @@ framework for every frame, for example, during an animation. | |
|
||
**How to fix it?** | ||
|
||
One way to avoid this error is to use the `Navigator` API | ||
to trigger the dialog as a route. In the following example, | ||
there are two pages. The second page has a | ||
dialog to be displayed upon entry. | ||
When the user requests the second page by | ||
clicking a button on the first page, | ||
the `Navigator` pushes two routes–one | ||
for the second page and another for the dialog. | ||
One way to avoid this error is instruct flutter to finish rendering the page | ||
before calling `showDialog()`. This can be done by using | ||
addPostFrameCallback() method. | ||
The following code illustrates this on the broken example just given: | ||
|
||
<?code-excerpt "lib/set_state_build.dart (solution)"?> | ||
```dart | ||
class FirstScreen extends StatelessWidget { | ||
const FirstScreen({super.key}); | ||
import 'package:flutter/material.dart'; | ||
|
||
void main() => runApp(const ShowDialogExampleApp()); | ||
|
||
class ShowDialogExampleApp extends StatelessWidget { | ||
const ShowDialogExampleApp({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return MaterialApp( | ||
theme: ThemeData( | ||
colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), | ||
home: const DialogExample(), | ||
); | ||
} | ||
} | ||
|
||
class DialogExample extends StatelessWidget { | ||
const DialogExample({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
//You can do this: | ||
WidgetsBinding.instance.addPostFrameCallback((_) { | ||
showDialog<void>( | ||
context: context, | ||
builder: (BuildContext context) { | ||
return const AlertDialog( | ||
title: Text('Alert Dialog'), | ||
); | ||
}, | ||
); | ||
}); | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('First Screen'), | ||
), | ||
body: Center( | ||
child: ElevatedButton( | ||
child: const Text('Launch screen'), | ||
onPressed: () { | ||
// Navigate to the second screen using a named route. | ||
Navigator.pushNamed(context, '/second'); | ||
// Immediately show a dialog upon loading the second screen. | ||
Navigator.push( | ||
context, | ||
PageRouteBuilder( | ||
barrierDismissible: true, | ||
opaque: false, | ||
pageBuilder: (_, anim1, anim2) => const MyDialog(), | ||
), | ||
); | ||
}, | ||
Comment on lines
-501
to
-513
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's a possible solution that doesn't involve stateful widgets (using the Navigator's onPressed: () async {
final NavigatorState navigator = Navigator.of(context);
navigator.pushNamed('/second');
// The default Material page transition duration is 300 ms.
await Future.delayed(Durations.medium2);
showDialog(
context: navigator.context,
builder: (context) => const MyDialog(),
);
}, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difficulty I'm having with implementing this is that my example code doesn't have an onPressed callback anywhere in it, and if I create a button in the Scaffold and put the showDialog in its onPressed:,, the "setState error during build" error disappears without the need of a addPostFrameCallback. I'm afraid that the broken code I give as an example is crazy - nobody in their right mind would write it. The actual code that caused me the problem was with pluto_grid, and I don't know how to create a sensible example of the error without it - and I can see that pluto_grid should not be included in a page like this. I guess the solution snippet in the current version is a good solution to a problem which was also too specific - which is why I didn't find it helpful. I suppose I've got into trouble because I'm trying to post a solution to a specific problem in a "common errors" page whose purpose has to be much more general. If I can't find a sensible example of broken code - which uses a callback like onPressed or onChanged and causes this particular error without using pluto_grid, I don't think I can make a sensible contribution. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for sharing your thoughts. I think you made a good point about the post-frame callback not being necessary here 🙂 |
||
), | ||
appBar: AppBar(title: const Text('This works')), | ||
body: const Center( | ||
child: Text('It Works'), | ||
), | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding the post-frame callback suggestion!
In its current state, though, I'm not sure if this is something we want to encourage in the documentation: scheduling a dialog in the build method means a new dialog is created each time the widget is rebuilt. If there's something like
Theme.of(context)
orMediaQuery.sizeOf(context)
in the build method, resizing the window or switching to dark mode might make a new dialog pop up every frame!The ideal setup would be to use a stateful widget (so the dialog could be scheduled inside
initState
) or make it part of anonPressed
callback (similar what the original example did). Perhaps this page could show both!