Flutter Firebase Storage Data doesn't show Data immediately - flutter

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.

Related

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

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

How to render image from Firebase without rebuilding?

I'm trying to implement profile image picking in my flutter app using Firebase Storage. I use image_picker to get the image and upload it to Firebase, get the download link and add the download link to the imgsrc field in the cloud firestore, from where I can render the NetworkImage.
Center(
child: Stack(
children: [
buildImage(),
Positioned(
bottom: 5,
right: 5,
child: GestureDetector(
onTap: showPhotoAlertDialog,
child: buildEditIcon(Color(0xff407bff))),
),
],
),
),
How can I get the default Icons.person kind image for when the user has no profile image, and get the image from the database otherwise?
The code I'm using right now is as follows:
Widget buildImage() {
return CircleAvatar(
backgroundImage: NetworkImage(loggedInUser.imgsrc ??
'https://th.bing.com/th/id/R.945f33b643f2ceffcdae90fb57c61854?rik=XcI0SYBgSefoCA&riu=http%3a%2f%2fgetdrawings.com%2ffree-icon-bw%2fanonymous-avatar-icon-19.png&ehk=5n%2buJG66CeLQZsmhaMt8gag5rXuM3TdebAL6W35K1E4%3d&risl=&pid=ImgRaw&r=0'),
backgroundColor: Colors.grey[350],
radius: 100,
);
}
I created an Alert Dialog widget to choose whether to choose the image from camera or from the gallery.
showPhotoAlertDialog() {
AlertDialog alert = AlertDialog(
title: Text("Upload from"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
imageFromCamera()
.then((value) => uploadFile())
.whenComplete(() => postSource());
setState(() {}); ----->
},
child: Text("Upload from camera"),
),
TextButton(
onPressed: () {
imageFromGallery().then((value) => uploadFile());
postSource();
setState(() {});
},
child: Text("Upload from gallery"),
)
],
),
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
To upload the image to storage and post the source to cloud firestore, I use the following methods:
Future uploadFile() async {
if (file == null) return;
final fileName = path.basename(file!.path);
final destination = 'files/$fileName';
task = FirebaseApi.uploadFile(destination, file!);
setState(() {});
if (task == null) return;
final snapshot = await task!.whenComplete(() {});
urlDownload = await snapshot.ref.getDownloadURL();
print('Download-Link: $urlDownload');
}
postSource() async {
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
await firebaseFirestore
.collection("users")
.doc(user?.uid)
.update({'imgsrc': urlDownload});
}
The link gets uploaded properly and I'm able to get the link in my NetworkImage, but it doesn't get rendered immediately. I have to close the parent drawer and open it again to get it. I call setState(){} as well after posting the source, but it doesn't make any difference. How can I get the image without having to close and open the drawer?
Any help would be appreciated!
Thanks
You also have to update image in model class or in this imgsrc also just add this line above setState in onPressed of TextButton.
loggedInUser.imgsrc = urlDownload;

flutter, trying to update a picture from Storage

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.

Flutter: Retrieve associated object from Future in FutureBuilder widget

I am fetching the user 'event manager id' data coming from a future of the object 'event'. I would like now to fetch a user using that id to display his name next the event. However, my FutureBuilder widget only takes into account one future (Event) and I am not able to retrieve that user's name based on that event since my fetchUser method will only return Future objects.
Any help is greatly appreciated.
Here's the FutureBuilder widget:
body: new FutureBuilder(
future: events,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Event> availableEvents = snapshot.data;
if (!snapshot.hasData) return CircularProgressIndicator();
return new ListView.builder(
scrollDirection: Axis.vertical,
padding: new EdgeInsets.all(6.0),
itemCount: availableEvents.length,
itemBuilder: (BuildContext context, int index) {
user = fetchUserbyId( // Here, user is of type Future<user> and I cannot retrieve info such as the name of that user
(availableEvents[index].managerId).toString());
return new Container(
margin: new EdgeInsets.only(bottom: 6.0),
padding: new EdgeInsets.all(6.0),
color: Colors.white,
child: Column(
children: <Widget>[
new Text('${availableEvents[index].name}',
style: TextStyle(
fontWeight: FontWeight.bold,
height: _height,
fontSize: 18)),
new Text('${availableEvents[index].description}',
style: TextStyle(height: _height)),
new Text('${availableEvents[index].address}',
style: TextStyle(height: _height)),
new Text('${availableEvents[index].datetime}',
style: TextStyle(height: _height)),
//new Text('${availableEvents[index].managerId}', style: TextStyle(height: _height)),
new FlatButton(
onPressed: null,
// Simply call joinEvent for event 'availableEvents[index]'
color: Colors.redAccent,
textColor: Colors.white,
disabledColor: Colors.red,
disabledTextColor: Colors.white,
padding: EdgeInsets.all(8.0),
splashColor: Colors.redAccent,
child: Text('Join!'),
)
],
));
},
);
}));
Here is the fetchUserByID method:
Future<User> fetchUserbyId(String id) async {
final response =
await http.get('https://url-here.com' + id);
//print("response : " + response.body);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
return User.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
If what I'm understanding is you have two asynchronous calls, where the second one needs the results of the first call to execute. The best way to go around this is to create a helper method, i.e. getData(). In this method you make your call to events and then use that to fetchUserbyId. This would result in your FutureBuilder looking something like this:
FutureBuilder(
future: getData()
builder: ... // get the results the same why you got your results from events in the given example.
);
Then in you getData() method it would look something like this:
Future<User> getData() async {
var availableEvents= await events; // not sure what your events data/method is
return fetchUserbyId((availableEvents[index].managerId).toString());
}
I think I answered your question, but if I missed it please comment.
Note: On a completely unrelated topic, you don't need the new keyword in Flutter anymore to instantiate objects. Hope that speeds up your development process!