Search code examples
flutterfirebasedartfirebase-authentication

I have a problem with a Init State when Login Out from Firebase


I have an app that loads a list of stores a client has activated in a subscription.

I use a SetState and InitState to load the information and it works, but when i go back to the main page and press the button to signout i get this error.

Flutter This widget has been unmounted, so the State no longer has a context (and should be considered defunct). Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active

This is the code i use to load the Client Stores page how i call the states, the part of retrieving the information works and it loads it on a ListView

late List<Map<String, dynamic>> items;
  bool isLoaded = false;
  var collection =
      FirebaseFirestore.instance.collection("ClientStores").snapshots();

  getStoresSubscribed() async {
    try {
      if (idCurrentSession != "" && usrClient == true) {
        List<Map<String, dynamic>> tempList = [];
        await for (var StoreSubscribedSnap in collection) {
          for (var StoreSubscribedDocs in StoreSubscribedSnap .docs) {
            if (StoreSubscribedDocs["StoreID"] != null) {
              String idStore = StoreSubscribedDocs["StoreID"];

              var collection2 = FirebaseFirestore.instance
                  .collection("ClientStores")
                  .doc(idStore)
                  .collection("Clients");

              var docSnapshot = await collection2.doc(idCurrentSession).get();

              if (docSnapshot.exists) {
                Map<String, dynamic>? data = docSnapshot.data();
                String clientIdStore = data?['StoreID'];
                var dataStore = await FirebaseFirestore.instance
                    .collection("Stores")
                    .where("StoreID", isEqualTo: clientIdStore)
                    .get();

                for (var element in dataStore.docs) {
                  tempList.add(element.data());
                }

                setState(() {
                  items = tempList;
                  isLoaded = true;
                });
              }
            }
          }
        }
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          backgroundColor: Colors.orangeAccent,
          content: Text(
            "$e",
            style: TextStyle(fontSize: 18.0),
          )));
    }
  }

  @override
  void initState() {
      super.initState();
      getStoresSubscribed();
  }

And this is how i go back to the main page using a backbutton in the app bar:

          leading: BackButton(
            onPressed: () {
              Navigator.of(context).pop();
            },

This is the function i call on the signout button on the main page:

Future<void> signout({required BuildContext context}) async {
    await FirebaseAuth.instance.signOut();
    await Future.delayed(const Duration(seconds: 1));
    Navigator.pushReplacement(
        context,
        MaterialPageRoute(
            builder: (BuildContext context) => const HomeScreen()));
  }

I tried to use If(Mounted) as i read in many answers but i still get error.

Maybe i am making wrong use of the state on this part or am i missing something?

any help will be appreciated.

thank you


Solution

  • This typically occurs when an asynchronous operation attempts to call setState after the widget has been disposed of. To prevent this, you can check if the widget is still mounted before calling setState. Here's how you can modify your getStoresSubscribed method:

    getStoresSubscribed() async {
      try {
        if (idCurrentSession != "" && usrClient == true) {
          List<Map<String, dynamic>> tempList = [];
          await for (var StoreSubscribedSnap in collection) {
            for (var StoreSubscribedDocs in StoreSubscribedSnap.docs) {
              if (StoreSubscribedDocs["StoreID"] != null) {
                String idStore = StoreSubscribedDocs["StoreID"];
    
                var collection2 = FirebaseFirestore.instance
                    .collection("ClientStores")
                    .doc(idStore)
                    .collection("Clients");
    
                var docSnapshot = await collection2.doc(idCurrentSession).get();
    
                if (docSnapshot.exists) {
                  Map<String, dynamic>? data = docSnapshot.data();
                  String clientIdStore = data?['StoreID'];
                  var dataStore = await FirebaseFirestore.instance
                      .collection("Stores")
                      .where("StoreID", isEqualTo: clientIdStore)
                      .get();
    
                  for (var element in dataStore.docs) {
                    tempList.add(element.data());
                  }
    
                  if (mounted) {
                    setState(() {
                      items = tempList;
                      isLoaded = true;
                    });
                  }
                }
              }
            }
          }
        }
      } catch (e) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              backgroundColor: Colors.orangeAccent,
              content: Text(
                "$e",
                style: TextStyle(fontSize: 18.0),
              )));
        }
      }
    }
    

    all I did was add if (mounted) check before calling setState or showing a SnackBar, you ensure that these operations are only performed if the widget is still part of the widget tree.

    Additionally, it's good practice to cancel any active listeners or subscriptions in the dispose method to prevent them from triggering after the widget has been disposed of. For example, if you have a stream subscription, you can manage it as follows:

    StreamSubscription? _subscription;
    
    @override
    void initState() {
      super.initState();
      _subscription = collection.listen((StoreSubscribedSnap) {
        // Handle the snapshot
      });
    }
    
    @override
    void dispose() {
      _subscription?.cancel();
      super.dispose();
    }
    

    This ensures that any active work is properly canceled when the widget is disposed, preventing potential errors related to unmounted widgets.

    i hope that's help