1

I want to show AlertDialog on click of drawer item

I'm using below code to use navigation drawer item in my flutter app

class HomePage extends StatefulWidget {
  final drawerItems = [
    DrawerItem("View Your account", Icons.account_circle),
    DrawerItem("Request", Icons.receipt),
    DrawerItem("Order", Icons.shopping_cart),
    DrawerItem("Report", Icons.report_problem),
    DrawerItem("Log out", Icons.info)
  ];

  @override
  State<StatefulWidget> createState() {
    return new HomePageState();
  }
}

class HomePageState extends State<HomePage> {
  int _selectedDrawerIndex = 0;
  bool visibilityTag = false;

  showAlertDialog(BuildContext context) {
    // set up the button
    Widget okButton = FlatButton(
      child: Text("OK"),
      onPressed: () {
        Navigator.of(context).pop();
      },
    );
    Widget cancelButton = FlatButton(
      child: Text("Cancel"),
      onPressed: () {
        Navigator.of(context).pop();
      },
    );

    // set up the AlertDialog
    AlertDialog alert = AlertDialog(
      elevation: 10,
      shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(20.0))),
      title: Text("ORICON"),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Text("Are you sure you want to logout?"),
          Padding(
            padding: const EdgeInsets.only(top: 20.0),
            child: Text(
              "1300 898 989",
              style: TextStyle(
                  color: Colors.blue,
                  fontWeight: FontWeight.bold,
                  fontSize: 20.0),
            ),
          ),
        ],
      ),
      actions: [okButton, cancelButton],
    );

    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return alert;
      },
    );
  }

  _getDrawerItemWidget(int pos) {
    switch (pos) {
      case 0:
        visibilityTag = false;
        return AccountDetails();
      case 1:
        visibilityTag = true;
        return RequestBin();
      case 2:
        visibilityTag = true;
        return OrderBin();
      case 3:
        visibilityTag = true;
        return Report();
      case 4:
        showAlertDialog(context);
        visibilityTag = false;
        return AccountDetails();
      default:
        return new Text("Error");
    }
  }

  _onSelectItem(int index) {
    if (index == 0) {
      visibilityTag = false;
    } else {
      visibilityTag = true;
    }
//    setState(() => _selectedDrawerIndex = index);
    _selectedDrawerIndex = index; // removed setState call
    Navigator.of(context).pop(); // close the drawer
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> drawerOptions = [];
    for (var i = 0; i < widget.drawerItems.length; i++) {
      var d = widget.drawerItems[i];
      drawerOptions.add(new ListTile(
        leading: Icon(d.icon),
        title: Text(d.title),
        selected: i == _selectedDrawerIndex,
        onTap: () => _onSelectItem(i),
      ));
    }

    Future<bool> customPop() {
      if (_selectedDrawerIndex == 0) {
        visibilityTag = false;
        return Future.value(true);
      } else {
        setState(() {
          visibilityTag = false;
          _selectedDrawerIndex = 0;
        });
        return Future.value(false);
      }
    }

    void navigateToHomeScreen() {
      if (_selectedDrawerIndex == 0) {
        visibilityTag = false;
      } else {
        visibilityTag = false;
        setState(() {
          _selectedDrawerIndex = 0;
        });
      }
    }

    return WillPopScope(
      onWillPop: customPop,
      child: Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.white,
            iconTheme: IconThemeData(color: Colors.black), //add this line here
            // here we display the title corresponding to the fragment
            // you can instead choose to have a static title
            title: Text(
              widget.drawerItems[_selectedDrawerIndex].title,
              style: TextStyle(color: Colors.black),
            ),
            actions: <Widget>[
              Padding(
                padding: const EdgeInsets.only(right: 10),
                child: Visibility(
                    visible: visibilityTag,
                    child: Row(
                      children: <Widget>[
                        IconButton(
                          icon: new Icon(Icons.arrow_back_ios),
                          color: Colors.grey,
                          onPressed: navigateToHomeScreen,
                        ),
                        Text(
                          "BACK",
                          style: TextStyle(color: Colors.grey),
                        )
                      ],
                    )),
              )
            ],
          ),
          drawer: Drawer(
            child: Column(
              children: <Widget>[
                UserAccountsDrawerHeader(
                    accountName: new Text("Nilesh Rathod"), accountEmail: null),
                Column(children: drawerOptions)
              ],
            ),
          ),
          body: _getDrawerItemWidget(_selectedDrawerIndex)),
    );
  }
}

But I'm getting below Exception

This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: Overlay-[LabeledGlobalKey<OverlayState>#7e1a8]
  state: OverlayState#72b8b(entries: [OverlayEntry#cf40a(opaque: false; maintainState: false), OverlayEntry#e10aa(opaque: false; maintainState: true), OverlayEntry#e0ccc(opaque: false; maintainState: false), OverlayEntry#5fdab(opaque: false; maintainState: true)])
The widget which was currently being built when the offending call was made was: HomePage
  dirty
  dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#c84d4]]
  state: HomePageState#982b0
User-created ancestor of the error-causing widget was: 
  MaterialApp file:///home/ctpl119/Documents/NEW_PROJECT/oricon/oricon/lib/main.dart:11:10
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3687:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3702:6)
#2      State.setState (package:flutter/src/widgets/framework.dart:1161:14)
#3      OverlayState.insertAll (package:flutter/src/widgets/overlay.dart:346:5)
#4      OverlayRoute.install (package:flutter/src/widgets/routes.dart:43:24)

I have already checked below Stack-overflow links

If need more information please do let me know. Thanks in advance. Your efforts will be appreciated.

Goku
  • 9,102
  • 8
  • 50
  • 81

2 Answers2

7

You can copy paste run full code below
You need WidgetsBinding.instance.addPostFrameCallback

code snippet

case 4:
        //showAlertDialog(context);
        WidgetsBinding.instance.addPostFrameCallback((_) {
          showAlertDialog(context);
        });
        visibilityTag = false;
        return AccountDetails();

working demo

enter image description here

full code

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class DrawerItem {
  String title;
  IconData icon;

  DrawerItem(this.title, this.icon);
}

class HomePage extends StatefulWidget {
  final drawerItems = [
    DrawerItem("View Your account", Icons.account_circle),
    DrawerItem("Request", Icons.receipt),
    DrawerItem("Order", Icons.shopping_cart),
    DrawerItem("Report", Icons.report_problem),
    DrawerItem("Log out", Icons.info)
  ];

  @override
  State<StatefulWidget> createState() {
    return new HomePageState();
  }
}

class HomePageState extends State<HomePage> {
  int _selectedDrawerIndex = 0;
  bool visibilityTag = false;

  showAlertDialog(BuildContext context) {
    // set up the button
    Widget okButton = FlatButton(
      child: Text("OK"),
      onPressed: () {
        Navigator.of(context).pop();
      },
    );
    Widget cancelButton = FlatButton(
      child: Text("Cancel"),
      onPressed: () {
        Navigator.of(context).pop();
      },
    );

    // set up the AlertDialog
    AlertDialog alert = AlertDialog(
      elevation: 10,
      shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(20.0))),
      title: Text("ORICON"),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Text("Are you sure you want to logout?"),
          Padding(
            padding: const EdgeInsets.only(top: 20.0),
            child: Text(
              "1300 898 989",
              style: TextStyle(
                  color: Colors.blue,
                  fontWeight: FontWeight.bold,
                  fontSize: 20.0),
            ),
          ),
        ],
      ),
      actions: [okButton, cancelButton],
    );

    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return alert;
      },
    );
  }

  _getDrawerItemWidget(int pos) {
    switch (pos) {
      case 0:
        visibilityTag = false;
        return AccountDetails();
      case 1:
        visibilityTag = true;
        return RequestBin();
      case 2:
        visibilityTag = true;
        return OrderBin();
      case 3:
        visibilityTag = true;
        return Report();
      case 4:
        //showAlertDialog(context);
        WidgetsBinding.instance.addPostFrameCallback((_) {
          showAlertDialog(context);
        });
        visibilityTag = false;
        return AccountDetails();
      default:
        return new Text("Error");
    }
  }

  _onSelectItem(int index) {
    if (index == 0) {
      visibilityTag = false;
    } else {
      visibilityTag = true;
    }
    setState(() => _selectedDrawerIndex = index);
    //_selectedDrawerIndex = index; // removed setState call
    Navigator.of(context).pop(); // close the drawer
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> drawerOptions = [];
    for (var i = 0; i < widget.drawerItems.length; i++) {
      var d = widget.drawerItems[i];
      drawerOptions.add(new ListTile(
        leading: Icon(d.icon),
        title: Text(d.title),
        selected: i == _selectedDrawerIndex,
        onTap: () => _onSelectItem(i),
      ));
    }

    Future<bool> customPop() {
      if (_selectedDrawerIndex == 0) {
        visibilityTag = false;
        return Future.value(true);
      } else {
        setState(() {
          visibilityTag = false;
          _selectedDrawerIndex = 0;
        });
        return Future.value(false);
      }
    }

    void navigateToHomeScreen() {
      if (_selectedDrawerIndex == 0) {
        visibilityTag = false;
      } else {
        visibilityTag = false;
        setState(() {
          _selectedDrawerIndex = 0;
        });
      }
    }

    return WillPopScope(
      onWillPop: customPop,
      child: Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.white,
            iconTheme: IconThemeData(color: Colors.black), //add this line here
            // here we display the title corresponding to the fragment
            // you can instead choose to have a static title
            title: Text(
              widget.drawerItems[_selectedDrawerIndex].title,
              style: TextStyle(color: Colors.black),
            ),
            actions: <Widget>[
              Padding(
                padding: const EdgeInsets.only(right: 10),
                child: Visibility(
                    visible: visibilityTag,
                    child: Row(
                      children: <Widget>[
                        IconButton(
                          icon: new Icon(Icons.arrow_back_ios),
                          color: Colors.grey,
                          onPressed: navigateToHomeScreen,
                        ),
                        Text(
                          "BACK",
                          style: TextStyle(color: Colors.grey),
                        )
                      ],
                    )),
              )
            ],
          ),
          drawer: Drawer(
            child: Column(
              children: <Widget>[
                UserAccountsDrawerHeader(
                    accountName: new Text("Nilesh Rathod"), accountEmail: null),
                Column(children: drawerOptions)
              ],
            ),
          ),
          body: _getDrawerItemWidget(_selectedDrawerIndex)),
    );
  }
}

class AccountDetails extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("AccountDetails");
  }
}

class RequestBin extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("RequestBin");
  }
}

class OrderBin extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("OrderBin");
  }
}

class Report extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("Report");
  }
}
chunhunghan
  • 51,087
  • 5
  • 102
  • 120
  • Hey thanks it worked, can explain what is the use of `WidgetsBinding.instance.addPostFrameCallback()` – Goku Dec 26 '19 at 09:47
  • we have to wait for the completion of build. there is a good a little long explain here. https://www.didierboelens.com/faq/week2/ – chunhunghan Dec 27 '19 at 00:51
0

You should make _selectedDrawerIndex a global variable (aka put it above the classes). You don't need to use setState to change the value of the variable. When you pop a route, it causes the widget to rebuild.

_onSelectItem(int index) {
  if (index == 0) {
    visibilityTag = false;
  } else {
    visibilityTag = true;
  }
  _selectedDrawerIndex = index; // removed setState call
  Navigator.of(context).pop();
}

Also, functions like showAlertDialog(context); should not be in a setState call.

So, in summary:

Remove setState calls that change the value of _selectedDrawerIndex and showAlertDialog.

showAlertDialog has a builder so there's no reason to call setState if it's going to rebuild anyways.

_getDrawerItemWidget(int pos) {
  switch (pos) {
    case 0:
      visibilityTag = false;
      return AccountDetails();
    case 1:
      visibilityTag = true;
      return RequestBin();
    case 2:
      visibilityTag = true;
      return OrderBin();
    case 3:
      visibilityTag = true;
      return Report();
    case 4:
      setState(() {
        visibilityTag = false;
      });
      showAlertDialog(context);
      return AccountDetails();
    default:
      return new Text("Error");
  }
}
Benjamin
  • 5,783
  • 4
  • 25
  • 49
  • I also tried the same the same approach but still same issue – Goku Dec 23 '19 at 13:47
  • Can you remove all the `setState` calls that call `showAlertDialog` and change `_selectedDrawerIndex` and update your question with the new code and also confirm you still face the same issue after a full restart of the app? – Benjamin Dec 23 '19 at 13:52
  • Now i'm not getting any exception but my `AlertDialog` is not showing – Goku Dec 23 '19 at 13:58
  • Move `showAlertDialog` into the `build` and take the other methods out of the `build` method. That way, you can remove the context parameter from the function since `build` already provides you with a context and see if you get the error. – Benjamin Dec 23 '19 at 14:07
  • if i move `showAlertDialog` inside the `build` then I'm not able to call `showAlertDialog` method [Please check this screenshot](https://i.stack.imgur.com/gnPtH.png) – Goku Dec 24 '19 at 04:48