Flutter Web: Images uploaded to firebase storage not showing preview - firebase

I am migrating one of my apps to the web with flutter for the first time. My app uses firebase packages to post images to firebase storage. We know "file path" doesn't work on the web so I had to change to byte. But on the firebase storage, I discovered that the images posted through bytes cannot be previewed. If you click on the token, rather than a preview, it will just download straight away.
Here is a pictorial illustration of what I mean:
Images, when clicked are supposed to be able to show a preview like this
But this is what I get
I see they are uploaded as documents instead of file.
Please how do I rectify this.
This is my code below
Future selectFile() async {
final result = await FilePicker.platform.pickFiles(allowMultiple: false, type: FileType.image);
if (result == null) return;
final path = result.files.single;
setState(() => imageFile = path);
}
//upload
Future upload() async {
if (imageFile == null) return;
final fileName = imageFile!.name;
final destination = 'FTV/Thumbnails/$fileName';
// task = FirebaseApi.uploadFile(destination, file!);
task = FirebaseApi.uploadBytes(destination, fileBytes!);
setState(() {});
if (task == null) return;
final snapshot = await task!.whenComplete(() {});
var urlDownload = await snapshot.ref.getDownloadURL();
// print('Download-Link: $urlDownload');
setState(() {
imageURL = urlDownload;
});
}

Related

firebase storage image upload fails the first time, but subsequent upload attempts load

so, I'm new to react native and firebase storage and I am trying to upload images to storage which fails the first time but if I try immediately afterwards it works with no issues at all and can see the photo in the storage.
The error I am getting on the first attempt is:
{"code":"storage/unknown","customData":{},"name":"FirebaseError","status_":400,"_baseMessage":"Firebase Storage: An unknown error occurred, please check the error payload for server response. (storage/unknown)","line":20330,"column":28,"sourceURL":"http://192.168.1.12:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false&strict=false&minify=false"}
This is the code below:
const submitPhoto = async () => {
let id = await auth.currentUser.uid;
// Create file metadata including the user id
var metadata = {
customMetadata: {
userUpload: id,
},
};
var ref;
try {
const response = await fetch(image);
const blob = await response.blob();
let imageName = image.substring(image.lastIndexOf('/') + 1);
setFileName(imageName);
ref = await firebase
.storage()
.ref('climbPhotos')
.child(filename)
.put(blob, metadata);
console.log(ref.metadata.name);
console.log(ref.metadata);
} catch (error) {
console.log(JSON.stringify(error));
Alert.alert('Server Error', 'Error has occurred on image upload');
}
setImage(null);
};
I am at a lost with this as like I said it occurs only on the first attempt of me opening that particular screen and uploading any image for the first time.
So I figured out what the issue was, and it's all to do with the upload using state values for the filename apparently even though the value was being set (i checked with console logs immediately after being set) the value wasn't always being passed correctly. whether this was the issue or not it seems my issue has been solved by removing states from the child.
const response = await fetch(image);
const blob = await response.blob();
let imageName = image.substring(image.lastIndexOf('/') + 1);
setFileName(imageName);
console.log(' vvvv ');
console.log(filename);
console.log(imageName);
console.log(image);
console.log(' ^^^ ');
var imageRef = firebase.storage().ref('climbPhotos').child(imageName);
26a51e0a-8ab8-4b62-aa4a-46a3d777e4fa.jpeg
file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FRockClimbingApp-cde1c400-dff9-44c8-8840-c83b731c56de/ImagePicker/26a51e0a-8ab8-4b62-aa4a-46a3d777e4fa.jpeg
as you can see the log shows the image and imageName but not the filename due to states being async

how to get url from firebase storage to save in firestore

i'am still new in flutter and firebase. I want to ask on how to get the URL file from storage and save it in firestore?
this is the code to upload file.
Future uploadFile() async {
if (file == null) return;
final fileName = basename(file.path);
final destination = 'files/$fileName';
task = FirebaseApi.uploadFile(destination, file);
setState(() {});
if (task == null) return;
final snapshot = await task.whenComplete(() {});
final urlDownload = await snapshot.ref.getDownloadURL();
print('Download-Link: $urlDownload');
}
another related class with the upload file

Firebase Storage upload picture from gallery Flutter (No error)

I want to upload a picture choosen from gallery to Firestore Storage, this is my first time but I read docs and took a look on the internet, this is what I wrote (it works without errors but doing nothing on the storage).
Code below, I tested imgFromGallery and it works maybe the problem are Storage functions.
Future<File> imgFromGallery() async {
try {
final ImagePicker _picker = ImagePicker();
final PickedFile imageFile =
await _picker.getImage(source: ImageSource.gallery, imageQuality: 50);
//If there is no image selected, return.
if (imageFile == null) return null;
//File created.
File tmpFile = File(imageFile.path);
//it gives path to a directory - path_provider package.
final appDir = await getApplicationDocumentsDirectory();
//filename - returns last part after the separator - path package.
final fileName = tmpFile.path.split('/').last;
//copy the file to the specified directory and return File instance.
return tmpFile = await tmpFile.copy('${appDir.path}/$fileName');
} catch (e) {
print(e);
return null;
}
}
//Save file to Storage function
Future<void> setPicture(String pathStorage) async {
try {
final File file = await imgFromGallery();
if (file == null) return;
FirebaseStorage.instance.ref(pathStorage).putFile(file);
return;
} catch (e) {
print(e);
}
}
P.S. : I would like to return the URL of the uploaded picture as return of "setPicture" but I still don't know how to do this.
You are just few steps away!
TaskSnapshot task = await FirebaseStorage.instance.ref(pathStorage).putFile(file);
After successful upload, you will get TaskSnapshot. That has a reference member ref.
String image_url = await task.ref.getDownloadURL();
This image_url is the URL of the uploaded picture.
ref: What is the the method to use onComplete function to add Images to Firebase Storage

Flutter Web Upload to Firestore

I am having issues with Flutter web and uploading Images to Firestore. I'm pretty sure the issue lies in the Image Picker, as the normal(mobile) image picker does not work for the web. The normal image picker returns a File, but the alternative image_picker_web returns an Image, which gets rejected on upload because it's expecting a Future<File>.
image_picker_web has an alternative to return a Uint8List which I have used, and then converted to a File via dart:html - and uploads fine, but the image is corrupted and not viewable.
Here's what I have done:
On Button Press - Pick Image as Uint8List > Convert to Image, Store in memory and Display on Screen
onPressed: () async {
//Upload Image as Uint8List
imageBytes = await ImagePickerWeb.getImage(asUint8List: true);
//Convert Uint8List to Image
_image = Image.memory(imageBytes);
//Show new image on screen
setBottomSheetState(() {
image = _image;
});
},
Convert Uint8List to File using dart:html File and name as users UID.png (PNG Uploaded)
imageFile = html.File(imageBytes, '${user.uid}.png');
Use Method to upload File
import 'dart:async';
import 'package:firebase/firebase.dart' as fb;
import 'package:universal_html/prefer_universal/html.dart' as html;
String url;
Future<String> uploadProfilePhoto(html.File image, {String imageName}) async {
try {
//Upload Profile Photo
fb.StorageReference _storage = fb.storage().ref('profilephotos/$imageName.png');
fb.UploadTaskSnapshot uploadTaskSnapshot = await _storage.put(image).future;
// Wait until the file is uploaded then store the download url
var imageUri = await uploadTaskSnapshot.ref.getDownloadURL();
url = imageUri.toString();
} catch (e) {
print(e);
}
return url;
}
Call method
location = await uploadProfilePhoto(imageFile, imageName: '${user.uid}');
Add data including Location to Firebase Database
//Pass new user ID through to users Collection to link UserData to this user
await AdminUserData(uid: user.uid).updateAdminUserData(name: userName, email: userEmail, profilephoto: location);
Everything is working OK, just the image seems to be corrupted, it also comes back at almost double the filesize, which obviously means the File isn't coming back as the Image..
This is an old post but in case someone still needs help with this as I have been searching around for hours to figure this out. This is how I am doing it.
Import image_picker_web. I am using version 2.0.3.
Use ImagePickerWeb.getImageInfo on a button ontap listener to get the image info.
var fileInfo = await ImagePickerWeb.getImageInfo;
Show the image using Image.memory in the widget tree. (optional)
Image.memory(fileInfo.data!,width: 180),
Create firebase upload location
final firebasefileLocation = firebaseStorageLocation.child('${DateTime.now()}_${fireInfo.fileName}');
Upload the image to firebase.
await firebasefileLocation.putData(img.data!);
So this is how my file looks to work for both phone and web. There is more information about this and how to select multiple images on the image_picker_web page. You can use the concepts from here to make it crossed-platformed with IOS and Android too.
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker_web/image_picker_web.dart';
class ImagePickerDemo extends StatefulWidget {
const ImagePickerDemo({Key? key}) : super(key: key);
#override
_ImagePickerDemoState createState() => _ImagePickerDemoState();
}
class _ImagePickerDemoState extends State<ImagePickerDemo> {
MediaInfo? _imageInfo;
Future<void> _pickImage() async {
var fileInfo = await ImagePickerWeb.getImageInfo; //get image
if (fileInfo.data == null) return; // user did not choose image.
setState(() {
_imageInfo = fileInfo; // save image
});
}
Future<void> _uploadImage() async {
if (_imageInfo == null) return;
final firebaseStorageLocation =
FirebaseStorage.instance.ref().child('product_images');
final imageInfo = _imageInfo as MediaInfo;
_imageInfo as MediaInfo;
final firebasefileLocation = firebaseStorageLocation
.child('${DateTime.now()}_${imageInfo.fileName!}');
await firebasefileLocation.putData(imageInfo.data!);
final urlToUseLater = await firebasefileLocation.getDownloadURL();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _pickImage, child: Text('Choose Image')),
ElevatedButton(
onPressed: _imageInfo == null ? null : _uploadImage,
child: Text('Upload Image')),
Image.memory(
_imageInfo!.data!,
width: 180,
)
],
);
}
}
I have not tried the alternatives you mentioned, but below has worked for me before on Flutter web and Firebase. The event listener for uploadInput works for most platforms. The last part regarding document.body.append will ensure that it works on Mobile safari as well.
Future<void> _setImage() async {
final completer = Completer<String>();
InputElement uploadInput = FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.accept = 'image/*';
uploadInput.click();
uploadInput.addEventListener('change', (e) async {
// read file content as dataURL
final files = uploadInput.files;
Iterable<Future<String>> resultsFutures = files.map((file) {
final reader = FileReader();
reader.readAsDataUrl(file);
reader.onError.listen((error) => completer.completeError(error));
return reader.onLoad.first.then((_) => reader.result as String);
});
final results = await Future.wait(resultsFutures);
completer.complete(results[0]);
});
document.body.append(uploadInput);
final String image = await completer.future;
widget.newImage = uploadInput.files[0];
// Upload to Firebase
uploadToFirebase(widget.newImage); // This is dart:html File
uploadInput.remove();
}
Then the upload to Firebase Storage:
uploadToFirebase(String imageName, File file) async {
Firebase.UploadTask task = storage.refFromURL('gs://.../images/' + imageName).put(file);
}
For the underlying question of:
"How to upload image bytes to Firebase Storage?"
Here is a possible implementation:
import 'dart:developer';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
/// Opens a file picker and uploads a single selected file to Firebase storage.
/// Returns a download URL if upload is successful or null if the operation is
/// aborted.
///
/// Throws an exception if more than one file is selected or the selected file
/// size exceeds 300KB
Future<String?> pickAndUploadFile() async {
final ref = FirebaseStorage.instance.refFromURL('gs://YOUR-PROJECT.appspot.com');
String? res;
final filePickerRes = await FilePicker.platform.pickFiles();
if (filePickerRes != null) {
if (filePickerRes.count == 1) {
final file = filePickerRes.files.single;
if (file.size > 300000) {
throw Exception('File must be less than 300KB');
}
final upTask = ref.child('uploads/${file.name}').putData(file.bytes!);
final snapshot = upTask.snapshot;
res = (await snapshot.ref.getDownloadURL()).toString();
} else {
throw Exception('only one file allowed');
}
}
log('downloadUrl: $res');
return res;
}
The result (snapshot.ref.getDownloadURL()) is a qualified URL you can use with any image widget that loads a URL.

Flutter: Restrict calling build method many times

I am implementing a chat application where users can share images. Each image is a stateful widget and each of them should get uploaded to the Firebase Storage as well.
My problem is, this flow works fine at the beginning of the app but when you upload another image, instead of the a single file, now 2 files are getting uploaded (1 new file and the file from the previous message).
I am pretty sure that this is something to do with keys so I provide an instance of UniqueKey as well but the problem is still there.
Let me explain my implementation and then provide the code:
I have 2 files; one is the chat screen, and the other one is a single message chip.
Chat screen keeps a list of message chips and does the rendering accordingly.
Message chip is stateful because, I want the user to see a progress while it is being uploaded to the server. After picking up a file from the device, an instance of message chip will get pushed to the array in the chat screen.
My code for the attach pic button in chat screen:
IconButton(
icon: Icon(Icons.attach_file),
onPressed: () async {
final File _file = await ImagePicker.pickImage(
source: ImageSource.gallery);
if (_file != null) {
//appending to the messages list
final sss = new MediaMessage(
key: UniqueKey(),
file: _file,
isImage: true,
threadId: widget._threadId,
);
setState(() {
_messages.add(sss);
});
}
},
)
and here is the code in my message chip file (including only the essentials)
void initState() {
super.initState();
//creating a file name eg: img_456985.jpg
final rand = Math.Random().nextInt(10000);
final fileExt = widget.file.path
.substring(widget.file.path.lastIndexOf('.'), widget.file.path.length);
_fileName = 'image_$rand$fileExt';
final StorageReference storeRef = FirebaseStorage.instance
.ref()
.child('threads')
.child(widget.threadId)
.child(_fileName);
final uploadTask = storeRef.putFile(widget.file);
uploadTask.events.listen((event) {
setState(() {
_uploadPercentage = event.snapshot.bytesTransferred.toDouble() /
event.snapshot.totalByteCount.toDouble();
});
print(_uploadPercentage);
});
uploadTask.onComplete.then((snapshot) {
setState(() {
_uploadStatus = UploadProgressStatus.complete;
});
});
}
Here is a demo GIF of this issue:
GIF image demo
Any suggestions/solutions would be appreciated.
Thank you
Null the file value after image uploading task might work.
like
widget.file=null
place it after uploadTask

Resources