How can I show a Dialog the very first time I load a page? How can I do something once a build is complete?

Question

Several times I received questions such as

  • How can I show a Dialog only the first time a page is displayed?
  • How can I get the dimensions of something that depends on the device dimensions and other constraints?
  • How can I do something once the build is complete?

Answer

All these questions have something in common, we have to wait for the completion of build before doing something…

Let’s take each of the questions, one by one, to explain how Flutter works…


How can I show a Dialog only the first time a page is displayed?

When you begin in Flutter and you need to display something on the screen at page load, it is very natural to think that you can trigger it from the initState() or even from the Widget build(BuildContext context)

Then you end up with a code that might look like one the following:

  ...
  @override
  void initState(){
    super.initState();
    showDialog(
      ...
    );
  }

or

  ...
  @override
  Widget build(BuildContext context){
    showDialog(
      ...
    );
    return Scaffold(
      ...
    );
  }

I think that everyone who started up with Flutter once thought it was the way to proceed… but you systematically received a debug console error message stating something like:

  • setState() or markNeedsBuild() called during build. This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets…
  • inheritFromWidgetOfExactType(_LocalizationsScope) or inheritFromElement() was called before _PageState.initState() completed…

What does this mean and why can I not do it this way?

In order to understand it, you need to get back to the basics and more precisely on the “StatefulWidget Lifecyle” (see my article Widget - State - Context - InheritedWidget).

When you are loading a Page (= “Route”), the latter is either a StatelessWidget or a StatefulWidget, but if you consider the initState() method, it is a StatefulWidget. So, let’s continue the explanation with a StatefulWidget.

So, when you ask Flutter to display a new Page (=Route), behind the scene, Flutter needs to render your page on your device screen. This is done during a process called “frame rendering” (I will explain all this in a next article).

During this frame rendering, related to the first time we want to display the new page, the Widget is inflated. This means that it creates an Element (= BuildContext) which starts its lifecyle and will call the following methods in sequence:

  • initState
  • didChangeDependencies
  • build

It is ONLY once the method build is complete, that a couple of milliseconds later the frame rendering of that page is complete and that you may request to display something else, in addition.

What does happen when I call showDialog() inside the initState()?

In fact, when Flutter executes the initState() method, it is in the middle of a frame rendering process.

When you invoke the showDialog() method, you request Flutter to try to find the nearest Overlay Widget in the Widgets tree, using the context as source of the research…

Bear in mind that the context is, in fact, the Element that corresponds to the Widget you are currently inflating and that during the call to the initState(), that element is not yet mounted (= not yet formally positioned in the tree). Therefore, that context cannot yet be used to look for the nearest Overlay, since not yet formally in the tree…

What does happen when I call showDialog() from the build(…) method?

When you are invoking the showDialog() from the build(BuildContext context) method, Flutter finds the nearest Overlay widget and asks it to insert a new OverlayEntry, which will be used as a container for the dialog you want to show.

As the Overlay needs to be refreshed (to render the dialog), it requests the Flutter Framework to rebuild it but… Flutter is already in a build process. Therefore, Flutter rejects the request and throws an exception.

So, what is the solution?

Flutter Framework has a convenient API to request a callback method to be executed once a frame rendering is complete. This method is:

WidgetsBinding.instance.addPostFrameCallback.


How to use the addPostFrameCallback?

Case 1: Show a dialog only the first time you display a page

...
@override
void initState(){
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_){
    showDialog(
      context: context, 
      ...
    );
  });
}

As the initState is only invoked the very first time you inflate the StatefulWidget, this method will never be called a second time.

Therefore, you may request the addPostFrameCallback to display your dialog from that method. The showDialog will be executed after the build is complete.

Case 2: Do something once the build is complete

This might be very useful to wait for the rendering to be complete to, for example, get the exact dimensions of Widget. To do this:

@override
Widget build(BuildContext context){
  WidgetsBinding.instance.addPostFrameCallback((_) => onAfterBuild(context));

  return Container(
    ...
  );
}

void _onAfterBuild(BuildContext context){
  // I can now safely get the dimensions based on the context
}

Conclusion

There exists plenty of cases where it is very convenient to use the addPostFrameCallback API and you might have already noticed that I used it in some of my articles…

I really hope you will find this explanation useful.