flutterdartstate-managementflutter-image-picker

Flutter Unhandled Exception: setState() called after dispose()


I know there are lot of questions mentioning the same issue. But none is helpful for me. I have a valuelistable builder which listens to a boolean value . I have a image picker to pick image from gallery or camera. When the user clicks on a either camera or gallery option I use setstate to change the valuenotifier to true.this is working fine. when the users picks the image from the mobile and then comes back to the app, I want to change the same valuenotifier to false. I am changing the boolean because I want to user loading effect based on the boolean. But I am getting the above error.

I know using dispose or if(mounted) will clear this issue. But using both is not executing the setstate as the State object is no longer present in the widget tree. I want to understand the logic behind this and also how to better handle my situation.

Here is my code snippet

 GestureDetector(
              onTap: () {
                imagePicker(context, isloading);
              },
              child: ValueListenableBuilder(
                valueListenable: isloading,
                builder: (context, value, child) => Container(
                  height: 120,
                  width: 120,
                  child: isloading.value
                      ? CustomTextStyles.shimmerEffect(
                          baseColor:
                              Theme.of(context).colorScheme.inversePrimary)
                      : Container()                    ),
              ),

Here is my imagepicker widget

imagePicker(context, ValueNotifier<bool> isLoading) {
  String uid = FirebaseAuth.instance.currentUser!.uid;
  showDialog(
context: context,
builder: (context) => AlertDialog(
    scrollable: true,
    title: const Text('Pick Image From'),
    content: ImagePickerWidget(
      uid: uid,
      isLoading: isLoading,
    )),
   );
  }


 class ImagePickerWidget extends StatefulWidget {
  final String uid;
 ValueNotifier<bool> isLoading;
 ImagePickerWidget({super.key, required this.uid, required this.isLoading});

@override
State<ImagePickerWidget> createState() => _ImagePickerState();
}

class _ImagePickerState extends State<ImagePickerWidget> {
@override
 Widget build(BuildContext context) {
  return Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Column(
      children: [
        SizedBox(
            height: 50,
            width: 50,
            child: IconButton(
                onPressed: () {
                  setState(() {
                    widget.isLoading.value = true;
                  });
                  pickImages(ImageSource.camera).then((value) {
                    setState(() {
                      widget.isLoading.value = false;
                    });
                  });

                  Navigator.pop(context);
                },
                icon: const Icon(Icons.camera_alt_outlined))),
        const Text('Camera')
      ],
    ),
    Column(
      children: [
        SizedBox(
            height: 50,
            width: 50,
            child: IconButton(
                onPressed: () {
                  setState(() {
                    widget.isLoading.value = true;
                  });
                  pickImages(ImageSource.gallery).then((value) {
                    setState(() {
                      widget.isLoading.value = false;
                    });
                  });
                  Navigator.pop(context);
                },
                icon: const Icon(Icons.image_outlined))),
        const Text('Gallery')
      ],
    )
  ],
);
}

  Future pickImages(ImageSource image) async {
  ImagePicker picker = ImagePicker();
   XFile? file = await picker.pickImage(source: image);
   if (file != null) {
  await storeImageInStorage(File(file.path));
  }
 }

  storeImageInStorage(File image) async {
    var ref = FirebaseStorage.instance
       .ref('ProfilePictures/${widget.uid}/profilePic');
    await ref.putFile(image);
    String downLoadurl = await ref.getDownloadURL();
    await FirebaseFirestore.instance
    .collection('Profiles')
    .doc(widget.uid)
    .update({'profilePic': downLoadurl});
    return true;
    }
     }

Solution

  • Try to await the picker came back in your application before setting state, like this:

    update pickImages method:

      Future pickImages(ImageSource image, BuildContext context) async {
       setState(() {
        widget.isLoading.value = true;
        });
    
       ImagePicker picker = ImagePicker();
       XFile? file = await picker.pickImage(source: image);
    
       if (file != null) {
         await storeImageInStorage(File(file.path));
       }
    `
           setState(() {
           widget.isLoading.value = false;
           });
       if (context.mounted) {
         Navigator.pop(context);
       }
      }
    

    an then call pickImage from you onPressed event with the context as second parameter.

    Not tested, but i think this should work.