gurkan stack
gurkan stack

Reputation: 225

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?

Upvotes: 0

Views: 11310

Answers (2)

Mahmoud Al-shehyby
Mahmoud Al-shehyby

Reputation: 444

When you use the context in the showDailog method this is the argument of the showDailog, so when you use Navigator.pop(context); the context is no longer exist, so to solve this problem you need to use the context of the build method that happens by using this.context instead of context

like this

TextButton(
  onPressed: () async {
    Navigator.pop(context);
      await this.context  //This line is problematic! 
            read<SignInBloc>()
            .deleteDatafromDatabase()
            .then((_) async =>
               await this.context.read<SignInBloc>().userSignout())
               .then(
                (_) => this.context.read<SignInBloc>().afterUserSignOut())
                 .then((_) {
                  Future.delayed(Duration(seconds: 1)).then((value) =>
                        nextScreenCloseOthers(context, WelcomePage()));
        },
      );
     },
     child: Text('YES').tr(),
),

I had problem similar to this and that's how I solve it, hope that's helps.

Upvotes: 0

Calvin Gonsalves
Calvin Gonsalves

Reputation: 2030

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())
            ],
          );
        });
  }

Upvotes: 7

Related Questions