flutter, trying to update a picture from Storage - flutter

I'm trying to upload, and update a picture from firebase storage. But for some reason this is not working properly.
This would be the scenario; A user take a picture from his camera, so this picture is uploaded to my storage, so after that I get the url picture, so I can use this like NetworkImage(url) to update the picture.
This is what's happening currently:
Let's say I don't have any picture.
I update my profile with a new picture. (This works perfectly).
Now let's say I want to update my profile picture, so I take another picture, let's call it A so I upload this one.
Nothing happens, picture doesn't change.
But If I try again with a B picture, for some reason the picture is updated with the A picture.
This would be my code and how I'm facing this feature.
I have the next method which is invoked when a user click over his picture. I wait for the result (value) so when I got the result I upload the picture. Then, when the picture is uploaded, I just call to setState and save the url into the _imageUrl variable.
After that, I change the "profilePic" attribute from my data base to true.
String _imageUrl = 'assets/profileDefault.jpg';
bool profilePicture = false;
io.File profilePic;
Future getFromCamara() async {
await ImagePicker().getImage(source: ImageSource.camera).then((value) {
profilePic = io.File(value.path);
FirebaseStorage.instance.ref().child('picture').putFile(profilePic);
}).then((result) {
var ref = FirebaseStorage.instance.ref().child('picture');
ref.getDownloadURL().then((loc) => setState(() => _imageUrl = loc));
});
try {
FirebaseFirestore.instance
.collection('Usuarios')
.doc(uid)
.update({'profilePic': true});
} catch (e) {
print(e.toString());
}
So now, using a StreamBuilder, I get the result of profilePic from my DataBase, if is True, I download the URL, and if don't, I just use the Asset default's pic.
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('Usuarios').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot1) {
if (!snapshot1.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
List<DocumentSnapshot> users = snapshot1.data.docs;
for (DocumentSnapshot user in users) {
if (user.id == userID) {
profilePicture = user['profilePic'];
}
}
if (profilePicture) {
FirebaseStorage.instance
.ref()
.child('picture')
.getDownloadURL()
.then((loc) => _imageUrl = loc);
} else {
_imageUrl = 'assets/profileDefault.jpg';
}
return Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 0,
child: Container(
color: Color.fromRGBO(255, 0, 0, 70),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * .55,
),
),
Positioned(
top: MediaQuery.of(context).size.height * .015,
left: 15,
right: 15,
child: Container(
width: MediaQuery.of(context).size.height * .90,
height: 300,
padding: EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () => _setPic(context),
child: CircleAvatar(
radius: 79,
backgroundColor: Color(0xFFFFFFFFF),
child: CircleAvatar(
radius: 75,
backgroundImage: profilePicture
? NetworkImage(_imageUrl)
: AssetImage(_imageUrl),
),
),
),
// More code...
What I'm doing wrong? Why my picture isn't getting updated?
Updated:
I tried this, so I can save in pictureTemp the picture's bytes, calling to setState to rebuild the widget and put the picture with Image.memory(pictureTemp). But it doesn't seem to work.
Uint8List pictureTemp;
io.File profilePic;
Future getFromCamara() async {
var pickedFile = await ImagePicker().getImage(source: ImageSource.camera);
var image = await pickedFile.readAsBytes();
FirebaseStorage.instance
.ref()
.child('picture')
.putFile(io.File(pickedFile.path));
setState(() {
pictureTemp = image;
});
child: CircleAvatar(
radius: 75,
backgroundImage: profilePicture
? pictureTemp == null
? AssetImage(_imageUrl)
: Image.memory(pictureTemp)
: AssetImage(_imageUrl),
),

Here when the image is picked you should directly set it or you can set it after the firebase upload is finished successfully.
But rather than loading an image from Firebase URL after the upload, you can directly load from the picked file as follows;
image = Image.memory(await pickedFile.readAsBytes())
This will instantly set the image and will save you a read call to Firebase. You should always try to minimize the Firebase Reads whenever possible.

Related

Preview Picked Image in Flutter

Hi I am using the image_picker package in flutter to pick images and store them to firestore.
I want to preview the image in a circle avatar before I actually upload it so how would I go about doing that?
This is the code I have to pick the image:
String imageUrl = '';
Future pickImage(ImageSource imageSource) async {
ImagePicker imagePicker = ImagePicker();
XFile? file = await imagePicker.pickImage(
source: imageSource);
if (file == null) return;
String uniqueFileName = DateTime
.now()
.millisecondsSinceEpoch
.toString();
//Getting a reference to storage root
Reference referenceRoot = FirebaseStorage.instance.ref();
Reference referenceDirImages = referenceRoot.child(
'images');
//Create a reference for the image to be stored
Reference referenceImageToUpload = referenceDirImages
.child(uniqueFileName);
try {
//Store the file
await referenceImageToUpload.putFile(File(file.path));
// Success: get download URL
imageUrl =
await referenceImageToUpload.getDownloadURL();
} catch (error) {
// Some error occurred
}
}
This is where I ask the user to pick the image either from the gallery or camera and then in the circle avatar I want them to preview the image before actually uploading it:
InkWell(
child: CircleAvatar(
backgroundColor: Colors.blue[900],
radius: 100,
),
onTap: () async {
return showDialog(context: context, builder: (context) {
return Center(
child: SizedBox(
height: 200,
child: AlertDialog(
backgroundColor: Colors.blue[900],
actionsPadding: const EdgeInsets.all(10.0),
content: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {
pickImage(ImageSource.gallery);
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.white)
),
child: const Text("Gallery",
style: TextStyle(
color: Colors.black
),)),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.white)
),
onPressed: () {
pickImage(ImageSource.camera);
},
child: const Text("Camera", style: TextStyle(
color: Colors.black
),),
)
],
),
),
),
),
);
}
);
}
),
Thanks!
Well you need to separate your functions into sub-functions, but first I will create this variable:
File? imageFile;
then it should be a function that picks that image from the device and update the state of the imageFile variable we made.
Future pickImage(ImageSource imageSource) async {
ImagePicker imagePicker = ImagePicker();
XFile? file = await imagePicker.pickImage(source: imageSource);
if (file == null) return;
setState(() {
imageFile = File(file.path);
});
}
and a method that returns the widget which will show the actual image we pick:
Image genratePreviowWidget(File file) {
return Image.file(file);
}
and in your UI, you should show it under an if condition like this:
Column(
children: [
if(imageFile != null) genratePreviowWidget(imageFile ),
],
),
now you have a method that pick the image and show it directly in your UI after it's done.
forwe still have that imageFile, so you can simply upload it ( confirm uploading it) from a separate method:
Future confirmUpload(File file) async {
String uniqueFileName = DateTime.now().millisecondsSinceEpoch.toString();
//Getting a reference to storage root
Reference referenceRoot = FirebaseStorage.instance.ref();
Reference referenceDirImages = referenceRoot.child('images');
//Create a reference for the image to be stored
Reference referenceImageToUpload = referenceDirImages.child(uniqueFileName);
try {
//Store the file
await referenceImageToUpload.putFile(file);
// Success: get download URL
imageUrl = await referenceImageToUpload.getDownloadURL();
} catch (error) {}
}
and you will need to call it when you want to upload it like this:
confirmUpload(imageFile);

how to show circular progress indicator while uploading multiple images to firebase using two screens

i am making a house management app i have to upload images of the property alongside other data related to the property so i am using two screens one for the general info about the house and the second one specifically to upload images
Form screen
Image Upload Screen
from the upload screen i am returning back a list of images to the form screen
// i am waiting for the list in the form screen
images = await Navigator.push(context, MaterialPageRoute(builder: (context) => AddPictures()));
// i am returning the list back from the upload screen
Navigator.pop(context,imageStrings);
I am failing to show circular progress indicator for some reason beyond my capacity to know itried all ways i know
this is the rest of the code
//outiside the widdget build i have two lists
List<XFile> imagesXFiles = []; //for raw image files from the gallery or camera
List<String> imageStrings = []; //for image links from the firebase storage
body: isLoading == true ? CircularProgressIndicator() : Column(
children: [
Expanded(
//the first grid is a button to let the user access camera or gallery
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0
),
itemCount: imagesXFiles.length + 1,
itemBuilder: (BuildContext context, int index) {
return index == 0 ? GestureDetector(
onTap: (){
// a function to pick images and add store them to the list "imagesXFiles"
_showPicker(context);
},
child: Container(
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(5.0),
),
child: Icon(
Icons.add,
color: Colors.black,
size: 30.0,
),
),
): Container(
child: Image(
image: FileImage(File(imagesXFiles[index-1].path)),
fit: BoxFit.fill
),
);
},
),
),
TextButton(
onPressed: ()async{
// for some reason the circular progress doesn't work i dont understand why
setState(() {
isLoading = true;
});
imageStrings = await uploadImages(imagesXFiles).whenComplete(() {
setState(() {
isLoading = false;
Navigator.pop(context,imageStrings);
});
});
},
child: Text("Upload",style: TextStyle(color: Colors.black,fontSize: 25),)),
],
),
here is the upload function that uploads the images to firebase
Future<List<String>> uploadImages(List<XFile> imagesXFiles) async {
imagesXFiles.forEach((image) async {
final storageRef = storage.ref().child(Random().nextInt(100).toString());
await storageRef.putFile(File(image.path));
String imageURL = await storageRef.getDownloadURL();
imageStrings.add(imageURL);
firebaseFirestore
.collection("housePictures")
.add({
"imageURL" : imageURL,
});
});
return imageStrings;
}
You can use forEach with Future as below.
await Future.forEach(imagesXFiles, (image) async {
final storageRef = storage.ref().child(Random().nextInt(100).toString());
await storageRef.putFile(File(image.path));
String imageURL = await storageRef.getDownloadURL();
imageStrings.add(imageURL);
FirebaseFirestore.instance
.collection("housePictures")
.add({
"imageURL" : imageURL,
});
});
You can’t use forEach statement in an async operation. It is not going to wait. Use a normal for statement. Example: for(var item in items) etc. That should fix your issue. If you really want to use a for each you need to use Future. foreach see this thread -> How to Async/await in List.forEach() in Dart

Flutter Firebase Storage Data doesn't show Data immediately

I'm currently working on a project in flutter where I want to store uploaded Data in Firebase Storage. This works fine but now I'm facing a problem with showing the data. I have to do a restart for showing the uploaded Data in my List.
I hope someone can help me with this issue.
onPressed: () async {
FilePickerResult? result = await FilePicker.platform
.pickFiles(allowMultiple: true);
if (result == null) return;
final path = result.files.single.path!;
setState(() {
});
final fileName = result.files.single.name;
storage
.uploadFile(path, fileName)
.then((value) => print('Done'));
},
This is my call function when pressing the button.
Future<void> uploadFile(String destination, String fileName) async {
final User? user = auth.currentUser;
final uid = user!.uid;
File file = File(destination);
try {
await storage.ref('$uid/$fileName').putFile(file);
} on firebase_core.FirebaseException catch (e) {
print(e);
}
}
This is my method for pushing the data into firebase storage.
Container(
width: MediaQuery.of(context).size.width - 15,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
child: FutureBuilder(
future: _loadImages(),
builder: (context,
AsyncSnapshot<List<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Row(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
final Map<String, dynamic> image =
snapshot.data![index];
return Card(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20)),
elevation: 0,
child: ListTile(
dense: false,
contentPadding: EdgeInsets.all(15),
leading: Image.network(
image['url'],
),
trailing: IconButton(
onPressed: () => _delete(image['path']),
icon: const Icon(
Icons.delete,
color: Colors.red,
),
),
),
);
},
),
),
],
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
),
And this is how I display my files in my app.
Looking forward for some help.
I want to see my data directly when uploading it into Firebase Storage.
If you want to show the newly uploaded image right away, you will have to force a refresh of the code that renders the list of images. If you're using a StatefulWidget to render the images, you could for example call setState() on that widget after the upload has completed. If you're using another state management approach, you'd do the equivalent for that approach.
But note that this will only work on the device of the user who uploaded the image. Any other devices will still only see the new image if they manually restart the rendering of the list of images.
A common way to work around this is to store the list of image URLs in one of Firebase's databases (Realtime Database or Cloud Firestore), and render it from there with a StreamBuilder. The StreamBuilder continues to listen for updates to the database, so if one user's image upload completes and they write the URL/path of the image to the database, that immediately refreshes the list of images not just on their device, but on all other connected devices too.

I tried to upload multi images, but it wasn't display preview photos

I tried to upload multi images, but it wasn't display preview photos.
Original code is can display only photo, but I modified the code then tried to upload multi images. Still cannot show to me.
Original Code, working well, but just show one image
SizedBox(
height: 250,
child: AspectRatio(
aspectRatio: 487 / 451,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(_file!),
fit: BoxFit.fill,
alignment: FractionalOffset.topCenter,
),
),
),
),
),
Then I tried to modified to this one
Expanded(
child: GridView.builder(
itemCount: selectedFiles.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(selectedFiles[index].path));
},
),
),
It wasn't show to me.
I can got the image list
Future<void> selectImage() async {
if (selectedFiles != null) {
selectedFiles.clear();
}
try {
final List<XFile>? imgs = await _picker.pickMultiImage();
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
}
print("image list : " + imgs.length.toString());
} catch (e) {
print(e.toString());
}
setState(() {});
}
Or I need to modify this code??
SimpleDialogOption(
padding: const EdgeInsets.all(20),
child: const Text('Choose from gallery'),
onPressed: () async {
Navigator.of(context).pop();
Uint8List file = await pickImage(ImageSource.gallery);
// final List<XFile>? imgs = await _picker.pickMultiImage();
// if (imgs!.isNotEmpty) {
// selectedFiles.addAll(imgs);
// }
setState(() {
_file = file;
});
},
),
For the GridView to display images the Ui has to rebuild
So when you add images to your list
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
}
you dont notify the UI to rebuild.
you can call an empty setstate below the selectedFiles to force UI to rebuild.
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
setState((){
})
}
For example when picking a single file
File? myfile;
pickFile()async{
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path);
setState((){
myFile=file;
})
} else {
// User canceled the picker
}}

Show picture from assets or from files Flutter

I want to show a picture inside a CircleAvatar, if the user never inserted a picture before then the default picture "empty_profile.png" will appear, otherwise if the user already inserted a picture from gallery or camera then the last one will be shown.
This is the code :
File _file = null;
File variable declared at the beginning.
Future<void> changeImage() async { //This will change the picture
File tmp = await imgFromGallery();
setState(() {
_file = tmp;
});
return;
}
The function above will change _file value to the file picked from gallery.
Widget myAvatar() {
return GestureDetector(
onTap: null,
child: CircleAvatar(
radius: 55,
backgroundColor: Color(0xffFDCF09),
child: CircleAvatar(
radius: 50,
child: Container(
child: _file == null
? AssetImage("empty_profile.png")
: FileImage(_file),
),
),
),
);
}
Finally if file is still null then the asset image is loaded, otherwise if a new picture is choosen then FileImage(_file) will show the picked image.
I have a lots of error because I don't know very well how to handle files, their paths and show images...Can you explain me how I should do?
To include static images in your Flutter project, place them inside the "assets/images" folder. Then, make sure to add this folder to your pubspec.yml:
flutter:
assets:
- assets/images/
Next, you may have errors related to trying to render an AssetImage inside a CircleAvatar. To render the file as an Image widget instead, use Image.asset(<path>). Your example could be written as:
Widget myAvatar() {
return GestureDetector(
onTap: null,
child: CircleAvatar(
radius: 55,
backgroundColor: Color(0xffFDCF09),
child: _file == null
? Image.asset("assets/images/empty_profile.png")
: _file
),
);
}
Finally, a great resource for user-selected images is the image_picker library: https://pub.dev/packages/image_picker. For example, a "Select from Gallery" button could invoke the following code, which allows the user to crop the image and saves it as an Image widget:
PickedFile image = await picker.getImage(
source: ImageSource.gallery, // to select from camera, use ImageSource.camera
maxHeight: 1024,
maxWidth: 1024,
imageQuality: 50
);
try {
File croppedImage = await ImageCropper.cropImage( // use platform-native image cropping
sourcePath: image.path,
cropStyle: CropStyle.circle,
maxWidth: 512,
maxHeight: 512
);
setState(() { // refresh state to render new profile image
_file = Image.file(croppedImage)
})
} catch (err) {
print(err)
}
You can use CachedNetworkImage PlugIn - Update other details as per your need. This code show Network Images, if not available will show Assets Image.
new Container(
width: 140.0,
height: 140.0,
child: ClipOval(
child: CachedNetworkImage(
imageUrl: _profile == null ? "" : Api.baseURL + _profile,
placeholder: (context, url) =>
Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => Image.asset(
"img/user.png",
fit: BoxFit.cover,
height: 140.0,
width: 140.0,
),
fit: BoxFit.cover,
height: 140.0,
width: 140.0,
),
),
),