Search code examples
iosflutterdartflutter-layoutflutter-widget

I'm getting "Looking up a deactivated widget's ancestor is unsafe." Error


I'm trying to delete data from firebase when user click the button.

await context line is problematic!

class SecurityOption extends StatelessWidget {//StatelessWidget
  const SecurityOption({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Divider(
          height: 3,
        ),
        ListTile(
          title: Text('security').tr(),
          leading: Container(
            height: 30,
            width: 30,
            decoration: BoxDecoration(
                color: Colors.red, borderRadius: BorderRadius.circular(5)),
            child: Icon(Feather.lock, size: 20, color: Colors.white),
          ),
          trailing: Icon(
            Feather.chevron_right,
            size: 20,
          ),
          onTap: () => _openDeleteDialog(context),
        ),
      ],
    );
  }

  _openDeleteDialog(context) {
    return showDialog(
        barrierDismissible: true,
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text('Delete data').tr(),
            content: Text('Are you sure?').tr(),
            actions: [
              TextButton(
                onPressed: () async {
                  Navigator.pop(context);
                  await context  //This line is problematic! 
                      .read<SignInBloc>()
                      .deleteDatafromDatabase()
                      .then((_) async =>
                          await context.read<SignInBloc>().userSignout())
                      .then(
                          (_) => context.read<SignInBloc>().afterUserSignOut())
                      .then((_) {
                    Future.delayed(Duration(seconds: 1)).then((value) =>
                        nextScreenCloseOthers(context, WelcomePage()));
                  });
                },
                child: Text('YES').tr(),
              ),
              TextButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: Text('cancel').tr())
            ],
          );
        });
  }


}

SignInBloc Class:

class SignInBloc extends ChangeNotifier {

...
...
...


  Future deleteDatafromDatabase () async{
    FirebaseFirestore _db = FirebaseFirestore.instance;
    await _db.collection('x').doc('y').delete();
  }


}

When I run the app & click the button, it deletes the data but gives an error.

Exception has occurred.

FlutterError (Looking up a deactivated widget's ancestor is unsafe. At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)

Besides, the app is freezing(is not closing). Clicks, swipes, etc. is not working...

How can I solve my problem?


Solution

  • You have identified correctly the problematic code. The problem is after you pop the Dialog, the dialog's context no longer exists but you are trying to access it. It is almost always better to not use the context that the Dialog builder provides because it can be dismissed at anytime. Even if you move the Navigator.pop to the end you have the barrierDismissible: true so the dialog can be dismissed and you will not be able to access the context. You can solve this issue by:

    1. Changing the context name that the dialog builder provides to say dialogContext and use this to pop Navigator.pop(dialogContext) and use the context passed to the _openDeleteDialog for the next operations.
    _openDeleteDialog(context) {
        return showDialog(
            barrierDismissible: true,
            context: context,
            builder: (dialogContext) {  <------- Change this
              return AlertDialog(
                title: Text('Delete data').tr(),
                content: Text('Are you sure?').tr(),
                actions: [
                  TextButton(
                    onPressed: () async {
                      Navigator.pop(dialogContext); <----- Use here
                      await context  <--- Keep as is
                          .read<SignInBloc>()
                          .deleteDatafromDatabase()
                          .then((_) async =>
                              await context.read<SignInBloc>().userSignout())
                          .then(
                              (_) => context.read<SignInBloc>().afterUserSignOut())
                          .then((_) {
                        Future.delayed(Duration(seconds: 1)).then((value) =>
                            nextScreenCloseOthers(context, WelcomePage()));
                      });
                    },
                    child: Text('YES').tr(),
                  ),
                  TextButton(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      child: Text('cancel').tr())
                ],
              );
            });
      }
    
    1. Pass a Function callback (VoidCallback) to the _openDeleteDialog and call it after the Navigator.pop.
    onTap: () => _openDeleteDialog(context, ()async {
                 await context  
                          .read<SignInBloc>()
                          .deleteDatafromDatabase()
                          .then((_) async =>
                              await context.read<SignInBloc>().userSignout())
                          .then(
                              (_) => context.read<SignInBloc>().afterUserSignOut())
                          .then((_) {
                        Future.delayed(Duration(seconds: 1)).then((value) =>
                            nextScreenCloseOthers(context, WelcomePage()));
                      });
                  } 
             ),
    
    
    _openDeleteDialog(context, VoidCallback onDelete) {
        return showDialog(
            barrierDismissible: true,
            context: context,
            builder: (context) {
              return AlertDialog(
                title: Text('Delete data').tr(),
                content: Text('Are you sure?').tr(),
                actions: [
                  TextButton(
                    onPressed: ()  {
                      Navigator.pop(context);
                     onDelete();
                    },
                    child: Text('YES').tr(),
                  ),
                  TextButton(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      child: Text('cancel').tr())
                ],
              );
            });
      }