How To Show A Popup In Your Flutter App With Riverpod

Flutter category image

Introduction

Displaying a popup is a rather trivial task for any seasoned Flutter developer. But when you are using Riverpod as state management solution, it can be a bit tricky. Here is my approach on how to show a popup in your Flutter app with Riverpod.

Why addPostFrameCallback doesn’t work

First, you might come up with this approach:

Dart
class MyView extends StatefulWidget {
  const MyView({super.key});

  @override
  State<MyView> createState() => _MyViewState();
}

class _MyViewState extends State<MyView> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await showDialog(
        context: context,
        builder: (context){
        return SomeDialogWidget();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Using the addPostFrameCallback function is a good way to perform actions after the first frame is rendered. The widget is build and the dialog appears on top of it. Great in theory, but this doesn’t work with Riverpod notifiers.

Here is the same code but this time, the build method relies on data from a Notifier.

Dart
class MyView extends ConsumerStatefulWidget {
  const MyView({super.key});

  @override
  ConsumerState<MyView> createState() => _MyViewState();
}

class _MyViewState extends ConsumerState<MyView> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await showDialog(
        context: context,
        builder: (context) {
          return SomeDialogWidget();
        },
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    final dataNotifier = ref.watch(someNotifier);

    return dataNotifier.when(
      data: (data) => Container(),
      loading: () => CircularProgressIndicator(),
      error: (e, st) => ErrorWidget(e),
    );
  }
}

The first build cycle will usually return the loading state. After the rendering, the addPostFrameCallback function is executed and the dialog is shown. However, as soon as the notifier changes its state, another rebuild is triggered (with either data or error state) and the dialog is gone. You might not even have seen it although it was there for a short amount of time!

It doesn’t matter if you have a stateless or stateful widget. Riverpod notifiers always start in the loading state before returning data or an error which leads to one inital build and one rebuild. The rebuild wipes your dialog.

Moving the addPostFrameCallback to the build method could work in some cases. However, for one-time popups this isn’t an option. The dialog would reappear every time the notifier state changes.

ref.listen is the solution

Riverpod offers a solution for this problem: ref.listen(). In short: It works similar to ref.watch() but doesn’t trigger a rebuild. For more details have a look at these articles here:

All we need is a little notifier that stores a boolean to indicate if the dialog can be shown. Here is an example:

Dart
final canShowDialogNotifier =
    NotifierProvider<DialogNotifierState, bool>(
      DialogNotifierState.new,
    );

class DialogNotifierState extends Notifier<bool> {
  @override
  bool build() {
    ref.keepAlive();
    return false;
  }

  void setState(bool newState){
    state = newState;
  }
}

You can also reverse the logic and let the notifier indicate if the dialog has already been shown. Just make sure to keep it alive or the dialog might pop up again without you wanting it 🙂

Next, we wire everything up in the build method of our widget:

Dart
class MyView extends ConsumerWidget {
  const MyView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen<bool>(canShowDialogNotifier, (prev, next) async {
      if (next) {
        await showDialog(
          context: context,
          builder: (context) {
            return SomeDialogWidget();
          },
        );
      }
    });

    final dataNotifier = ref.watch(someNotifier);

    return dataNotifier.when(
      data: (data) => Container(),
      loading: () => CircularProgressIndicator(),
      error: (e) => ErrorWidget(e),
    );
  }
}

next contains the new notifier value, prev the old one. You can add more logic here to decide if a dialog should be triggered. Change the notifier state from false to true and the dialog will appear.

💡 Tip

ref.listen requires a state change.

It does not react on the value provided by the build method of the notifier!

You can even remove the state from the widget (StatefulConsumerWidget -> ConsumerWidget) with this approach. No need for the initState method anymore!

Conclusion

In this article you learned how to show a popup in your Flutter app with Riverpod. As we have seen, addPostFrameCallback doesn’t work in every case but with ref.listen, there is an easy alternative to make it work again.


Want More Flutter Content?

Join my bi-weekly newsletter that delivers small Flutter portions right in your inbox. A title, an abstract, a link, and you decide if you want to dive in!

Flutter ❤️ Firebase

Get started with Firebase and learn how to use it in your Flutter apps. My detailed ebook delivers you everything you need to know! Flutter and Firebase are a perfect match!

Become A Testing Expert!

Become a proficient Flutter app tester with my detailed guide. This ebook covers everything from unit tests over widget tests up to dependency mocking.