Diego Vin
Diego Vin

Reputation: 293

How to show a dialog inside a futurebuilder?

I want to show a dialog if I receive an error in a futurebuilder.

If I receiver an error, I want to show a dialog and force the user to click on the button, so that he can be redirected to another page.

The problems seems to be that it is not possible to show a dialog while widget is being built.

FutureBuilder(
      future: ApiService.getPosts(),
      builder: (BuildContext context, AsyncSnapshot snapShot) {
        if (snapShot.connectionState == ConnectionState.done) {
          if (snapShot.data.runtimeType == http.Response) {
            var message =
              json.decode(utf8.decode(snapShot.data.bodyBytes));

    showDialog(
     context: context,
     barrierDismissible: false,
     builder: (context) {
       return AlertDialog(
        content: Text(message),
        actions: <Widget>[
          FlatButton(
            child: Text("Ok"),
            onPressed: () => null",
          )
        ],
      );
    });
  }
     return ListView.separated(
            separatorBuilder: (BuildContext context, int index) {
              return Divider(
                color: Colors.grey,
                height: 1,
              );
            },
            itemBuilder: (BuildContext context, int index) {
              return _buildPostCard(index);
            },
            itemCount: snapShot.data.length,
          );
     return Center(
          child: CircularProgressIndicator(),
        );
      },
    )

If I return the AlertDialog alone, it works. But I need the showDialog because of the barrierDismissible property.

Does any one know if that is possible? Also, is this a good way to handle what I want?

Thanks

UPDATE

For further reference, a friend at work had the solution.

In order to do what I was looking for, I had to decide which future I was going to pass to the futureBuilder.

Future<List<dynamic>> getPostsFuture() async { 
try {
    return await ApiService.getPosts();
  } catch (e) {

     await showDialog(
 context: context,
 barrierDismissible: false,
 builder: (context) {
   return AlertDialog(
    content: Text(message),
    actions: <Widget>[
      FlatButton(
        child: Text("Ok"),
        onPressed: () => null",
      )
    ],
  );
});
  }
}

}

Then in the futureBuilder I would just call

FutureBuilder(
  future: getPostsFuture(),

Thanks

Upvotes: 11

Views: 8834

Answers (3)

Jitesh Mohite
Jitesh Mohite

Reputation: 34250

setState() or markNeedsBuild() called during build. This exception is allowed because the framework builds parent widgets before its children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.

So to avoid that Future Callback is used, which adds a call like this EventQueue.

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  Future futureCall() async {
    await Future.delayed(Duration(seconds: 2));
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: futureCall(),
      builder: (_, dataSnapshot) {
        if (dataSnapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        } else {
          Future(() {  // Future Callback
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Employee Data'),
                      content: Text('Do you want to show data?'),
                      actions: <Widget>[
                        FlatButton(
                            onPressed: () =>
                                Navigator.of(context).pop('No'),
                            child: Text('NO')),
                        FlatButton(
                            onPressed: () =>
                                Navigator.of(context).pop('Yes'),
                            child: Text('YES'))
                      ],
                    ));
          });
          return Container();
        }
      },
    );
  }
}

Upvotes: 2

georkings
georkings

Reputation: 735

To avoid setState() or markNeedsBuild() called during build error when using showDialog wrap it into Future.delayed like this:

Future.delayed(Duration.zero, () => showDialog(...));

Upvotes: 15

RoyalGriffin
RoyalGriffin

Reputation: 2007

The builder param expects you to return a Widget. showDialog is a Future. So you can't return that.
You show Dialog on top of other widgets, you can't return it from a build method that is expecting a widget.
What you want can be implemented the following way.
When you receive an error, show a dialog on the UI and return a Container for the builder. Modify your code to this:

if (snapShot.data.runtimeType == http.Response) {
          var message =
          json.decode(utf8.decode(snapShot.data.bodyBytes));

          showDialog(
              context: context,
              barrierDismissible: false,
              builder: (context) {
                return AlertDialog(
                  content: Text(message),
                  actions: <Widget>[
                    FlatButton(
                        child: Text("Ok"),
                        onPressed: () => null",
                    )
                  ],
                );
              });
          return Container();
        }

Upvotes: -2

Related Questions