-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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 1 commit
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 |
---|---|---|
|
@@ -443,27 +443,48 @@ attempting to trigger a `Dialog` from within the | |
immediately show information to the user, | ||
but `setState` should never be called from a `build` method. | ||
|
||
The following snippet seems to be a common culprit of this error: | ||
The following code illustrates a common culprit of this error: | ||
|
||
<?code-excerpt "lib/set_state_build.dart (problem)"?> | ||
```dart | ||
Widget build(BuildContext context) { | ||
// Don't do this. | ||
showDialog( | ||
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(), | ||
); | ||
554richard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
class DialogExample extends StatelessWidget { | ||
const DialogExample({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// Don't do this: | ||
showDialog( | ||
context: context, | ||
builder: (context) { | ||
builder: (BuildContext context) { | ||
return const AlertDialog( | ||
title: Text('Alert Dialog'), | ||
); | ||
}); | ||
|
||
return const Center( | ||
child: Column( | ||
children: <Widget>[ | ||
Text('Show Material Dialog'), | ||
], | ||
), | ||
); | ||
}, | ||
); | ||
return Scaffold( | ||
appBar: AppBar(title: const Text('This does not work')), | ||
body: const Center( | ||
child: Text('Does not Work'), | ||
), | ||
); | ||
} | ||
} | ||
``` | ||
|
||
|
@@ -475,43 +496,49 @@ 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 implementing the showDialog. This can be done by using | ||
554richard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
addPostFrameCallback() method. The following code illustrates this on the broken example just given: | ||
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. Generally we want files to have lines of <= 80 characters. (more info here) 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. I split line 501 into two in my fork of the code, but I'm not sure if this change makes its way automatically into the PR, or whether I need to do something to update it. 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. It looks like this PR is using the |
||
|
||
<?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'), | ||
); | ||
}, | ||
); | ||
}); | ||
Comment on lines
+507
to
+517
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 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 The ideal setup would be to use a stateful widget (so the dialog could be scheduled inside |
||
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.
If all the code required for running an app is being added here, maybe it could be structured as an interactive DartPad sample! I'm not sure exactly how it works but I did see another page that was formatted this way:
website/src/content/cookbook/design/tabs.md
Lines 96 to 136 in 93fe19a
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.
That's a great idea, but I don't know how I'd be able to test the functionality before submitting it for review. I can get common-errors.md onto my PC, but how would I serve it to myself for testing, especially with the an interactive DartPad in place?
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.
If the goal is to test out your sample code, feel free to copy-paste it into the existing TabBar demo :)