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;
}
}
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.