For each Activity, users will be able to upload an image from the device gallery. For that, you’ll use the ImagePicker plugin.
So, in the activity_detail_screen.dart file, in the State class, first declare a nullable Image, called activityImage.
Image? activityImage;
Then declare a nullable XFile called galleryImage.
XFile? galleryImage;
A CrossFile is a “cross-platform File abstraction”: it contains the bytes of a file, and its path. We use this as it’s the return file type you get when you pick a file with the image_picker library. OK, add a new method, called choosePicture. As usual, this will be asynchronous. Declare a final, called picker, that takes an instance of ImagePicker.
Next, declare a final, called image, and set it to await picker.pickImage. The pickImage method takes a source, and here you can choose the origin for the image: in this case choose Imagesource.gallery.
This returns an XFile object. Finally call setState and set galleryImage to take the image that’s been selected.
Future choosePicture() async {
final _picker = ImagePicker();
final image = await _picker.pickImage(source: ImageSource.gallery);
setState(() {
galleryImage = image;
});
}
This method allows your users to choose an image from the gallery, but this image cannot be uploaded to the cloud yet, so let’s also create a method for that. As usual this is asynchronous, so it returns a Future: let’s call it uploadPicture.
In the method, first let’s check wheter an image has already been set. So, if galleryImage is NOT null, declare a final, called File, that takes an instance of a FIle object: here just pass the path of galleryImage. Here we can use the bang operator, as we’ve just checked that galleryImage is not null. Next call the helper uploadImage method, passing the file.
Future uploadPicture() async {
if (galleryImage != null) {
final file = File(galleryImage!.path);
helper.uploadImage(file);
}
}
In this case, nothing happens if galleryImage is null. OK, now that our methods are ready, let’s call them in the user interface. In the initState method check whether the current activity imagePath is not null, or does not contain an empty string.
If these conditions are met, call the getImage method to retrieve the image for this activity passing the current imagePath. In the then callback, set the activityImage, so that it calls the Image network constructor, passing the value parameter passed to the function. Then call setState to update the current view.
if (widget.activity.imagePath != null && widget.activity.imagePath != '') {
helper.getImage(widget.activity.imagePath!).then((dynamic value) {
activityImage = Image.network(value.toString());
setState(() {});
});
}
In the Build method check if both galleryImage and activityImage are null: this happens when a picture has not been chosen yet.
In this case, just call an image from the assets folder: this image was already available in the started project for this tutorial.Otherwise, check if galleryImage is NOT null, but activityImage is null. This happens when an image has been chosen but not shown to the user yet.
In this case, let’s set the activityImage to take an image, calling the image.file constructor, and passing a File instance, that takes the path of galleryIMage.
if (galleryImage == null && activityImage == null) {
activityImage = Image.asset('assets/running.png');
} else if (galleryImage != null && activityImage == null) {
activityImage = Image.file(File(galleryImage!.path));
}
Now we need to show the image that was chosen: so, in the build Method, in the Column widget, let’s add a SizedBox, with a height of 100. For its child, take activityImage.
SizedBox(
height: 100,
child: activityImage,
),
Under the image, add another sizedbox that contains an iconButton that will allow you to choose an image from the gallery. It will have a width of 100; the height will also be the same. Its child is an IconButton, with an icon of Icons.image and an onpressed that calls the choosePicture method.
SizedBox(
width: 100,
height: 100,
child: IconButton(
icon: const Icon(Icons.image),
onPressed: choosePicture,
),
),
One last step here: when users save the current activity, we also want to save the picture. So, in the floatingActionButton onpressed method, also add a call to uploadPicture().
onPressed: () {
saveUser();
uploadPicture();
OK, let’s have a look at the app. Choose an activity from the list and notice that now the screen displays a default image for this activity. Great, now let’s press the iconButton at the bottom of the screen: this allows chossing an image from the gallery. Choose an image and notice that now the new image is displayed. Save the current activity.
Now from the list get back to the activity you just saved. The image is still there, as you would expect. Great. One last check. Get to the firebase console, and in the storage bucket see that the image you took from the device gallery has been uploaded here. This measn everything is working as expected!
Now we need to delete images from this bucket. Let’s deal with that next!