TL;DR
context
is the widget in the tree; it will only be mounted to the tree after inflation. Hence, delay things that need to look up stuff in the render tree starting at context
to after the widget had been mounted to the tree; this is the case after the first render frame:
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
...
);
});
}, []);
Not too long, I want to understand and read it
Understanding the fundamentals of the render process of a widget will shed light on the darkness.
Why can't I show a dialog in useEffect
/initState
?
First of all, note that this is not a hooks-related topic, but in more general, the error would also show up, if you try to call showDialog
(or similiar) in the initState
method of a stateful widget. Here is why:
context
refers to the location of the widget in the tree. Before flutter can put the widget in the tree it needs to inflate the widget. This inflation is the first render frame of the widget and during this inflation process flutter will call initState
to set up all parameters of the widget in order to then call build(context)
to render the widget and then flutter would mount the context (i.e. the widget) to the tree. The hooks lib analog is useEffect((){}, [])
which also runs during this inflation process.
When calling showDialog(context: context, ...)
flutter needs to lookup the next Overlay
widget in the render tree and it will walk up the render tree starting from the given context
, but during inflation the context (i.e. the widget) is not yet mounted to the tree. Hence, you see this error.
But how can I show a dialog (or similar) on the first render?
Well, you cannot do it during the first render, but you can do it after the inflation process is completed and the widget is mounted to the tree. There are several ways of doing it.
use addPostFrameCallback
to delay execution of the callback to after the rendering of the current frame:
@override
void initState(){
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
...
);
});
}
use a lib like https://pub.dev/packages/after_layout . It is a mixin for stateful widgets that provides a method:
@override
void afterFirstLayout(BuildContext context) {
showDialog(context: context, ...);
}
How can I do it during useEffect
?
Again, you cannot look up things in the render tree starting from context
when context
is not mounted to the tree. This means, you cannot do it during the effect, but must delay execution to after the widget has been mounted to the tree:
@override
Widget build(BuildContext context) {
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
...
);
});
}, []);
}