Flutter Web Upload to Firestore - firebase

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.

Related

Upload image from URL to Firebase Storage using flutter

I want to upload image to firebase storage from a link using flutter so if anyone can tell me how can i do that.
Right now i am using imagePicker to pick images from mobile phone but now i wanted to upload the pics from URL i have searched for it and there is answer for javascript or other but not for flutter and i want it in flutter
Thanks
You have to first download the image from the link and save it in you temporary directory and then upload into firebase as you are already uploading it from image picker
here is the sample code that will help you to understand better
download(String url, String fileName) async {
try {
var per = await askPermission();
if (per!.isGranted) {
const path =
'/storage/emulated/0/Download/'; //you can use temporary
directory
final file = File('$path/$fileName');
await dio!.download(siteUrl + url, file.path,
onReceiveProgress: (rec, total) {
isLoading = true;
print(rec);
});
Get.snackbar('Success', 'File downloaded successfully',
backgroundColor: Colors.green.withOpacity(0.4));
} else {
Get.snackbar('Error', 'please grant storage permission',
backgroundColor: Colors.red.withOpacity(0.4));
}
} catch (e) {
pr.close();
Get.snackbar('Error', '${e.toString}',
backgroundColor: Colors.red.withOpacity(0.4));
print(e);
}
}
i use dio and Permission handler packages
from your linked Question, You can store the urls in your database else if you really want to store them in your firebase storage you have to download them and save to firebase. You can use plugins like flutter_downloader,
or use dio /http to download then upload to firebase
this has worked for me
`
class ImageUrlToStorage extends StatefulWidget {
const ImageUrlToStorage({Key key}) : super(key: key);
#override
State<ImageUrlToStorage> createState() => _ImageUrlToStorageState();
}
class _ImageUrlToStorageState extends State<ImageUrlToStorage> {
Dio dio;
bool isLoading = false;
final url =
'https://images.pexels.com/photos/733853/pexels-photo-733853.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500';
_save() async {
var status = await Permission.storage.request();
if (status.isGranted) {
var response = await Dio()
.get(url, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60,
name: "hello");
print(result);
}
}
#override
Widget build(BuildContext context) {
return ButtonWidget(
onClicked: () {
_save();
},
text: 'Upload Image to Storage',
);
}
}
`
I have used dio,image_gallery_saver,and Permission_handler

Correct order to upload images on firebase

To upload images for an object created by a user I store the images (selected by the user) in an array 'imagesList' as a File. When the user clicked upload (whole object) the following method saves the data on firebase:
TextButton(
onPressed: () async {
await uploadImage();
await jobService.createJob(Job(
titleTextEditorController.text.trim(),
category,
false,
false,
finalImageList));
},
child: Text('upload')),
The List finalImageList is filled in the first method 'uploadImage()'. I sourced it out in another method to get the await statement. The Code:
uploadImage() async {
for (int i = 0; i < imageList.length; i++) {
_imageFile = imageList[i];
String fileName = Path.basename(_imageFile!.path);
Reference reference =
FirebaseStorage.instance.ref().child('uploads/$fileName');
firebase_storage.SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': fileName});
UploadTask uploadTask = reference.putFile(_imageFile!);
uploadTask.whenComplete(() async {
try {
imageUrl = await reference.getDownloadURL();
print('imageUrl' + imageUrl);
finalImageList.add(imageUrl);
} catch (onError) {
print("Upload Error");
}
});
await Future.value(uploadTask)
.then((value) => {print('Upload file path ${value.ref.fullPath}')})
.onError((error, stackTrace) =>
{print('Upload file path error ${error.toString()}')});
}
}
But the method is not fast enough to store the imageUrl's in finalImageList, so the Images is online but its not connected to the object in firebase. Is there a possibility to upload it immediately or the save the imageUrl correctly? Or is my code just in the wrong order?
The FlutterFire UploadTask class extends Future, which means that you can use await on it to wait until the upload is done.
That means you can write your code much simpler as:
await reference.putFile(_imageFile!);
imageUrl = await reference.getDownloadURL();
finalImageList.add(imageUrl);
print('Upload file path ${value.ref.fullPath}')
With this change, your uploadImage will only complete after the download URL was added to finalImageList.

How to add multiple images in flutter (firebase)

how I can add multiple images and store them in an array in firebase ?
every container has a + button so the user can add single image per container then I WANT TO STORE them in my firebase
any help would be greatly appreciated ^^
what you are showing in your image is fbCloudStore which is use to store information in json structure.
To store the images you may use fbStorge.
here's an snippet I use on my project:
Future<String?> uploadFile({
required FilePickerResult file,
required String fileName,
required FirebaseReferenceType referenceType,
}) async {
try {
final ref = _getReference(referenceType);
final extension = path.extension(file.files.first.name);
final _ref = ref.child(fileName + extension);
late UploadTask uploadTask;
if (kIsWeb) {
uploadTask = _ref.putData(file.files.first.bytes!);
} else {
uploadTask = _ref.putFile(File(file.files.single.path!));
}
var url;
await uploadTask.whenComplete(
() async => url = await uploadTask.snapshot.ref.getDownloadURL());
print(url);
return url;
} on FirebaseException catch (_) {
print(_);
}
}
Note that I'm returning the URL of fbStorage in order to associate it with fbCloudStorage
final url =
await FirebaseStorageSer.to.uploadFile(
file: result,
fileName: STORAGE_USER_PROFILE_PICTURE,
referenceType: FirebaseReferenceType.user,
);
if (url != null) {
await FirebaseAuthSer.to
.updateUserProfile(photoUrl: url);
}
FirebaseReferenceType.user is just a simple enum to simplify ref targets.

flutter, Firebase Firestore I can't add element in the list

i'm trying to add a userID in the _thumbnailList
but it doesn't work.
I have no idea
List<DocumentSnapshot> _thumbnailList;
List<DocumentSnapshot> get thumbnailList => _thumbnailList;
...
void addRecommendListLocalAndServerInSearchProduct(
{#required String productId,
#required String loginUserUid,
#required List<Product> productLIst}) async {
List<dynamic> recommendList = [];
String uploaderId;
_thumbnailList.forEach((element) {
if (element.data['productId'] == productId) {
print(
'thumbnail recommendUidList is ${element.data['recommendUidList']}');
✅ element.data['recommendUidList'].add('dasda'); //for testing
print('🔍︎thumbnail after added ${element.data['recommendUidList']}');
}
});
...
I call the method like this.
// user already recommend this
if (_isRecommend) {
setState(() => _isRecommend = false);
productsProvider.removeRecommendListLocalAndServerInSearchProduct(
productId: _productId,
productLIst: _loadedProducts,
loginUserUid: _loginUserId,
);
}
// user haven't recommend this
else {
setState(() => _isRecommend = true);
productsProvider.addRecommendListLocalAndServerInSearchProduct(
productId: _productId,
loginUserUid: _loginUserId,
productLIst: _loadedProducts,
);
}
recommendUidList is List
after I print it, in console there is a just empty List
like [ ]
you guys any idea about this bug ? any. clue?
Are you trying to upload a single document, directly to your firestore?
In that case use this source code here:
import 'dart:io';
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
import 'package:path/path.dart';
typedef OnCompleteFirebaseUpload = Function(String cloudPath);
void uploadDocument(String filePath, OnCompleteFirebaseUpload complete,
Function onError) async {
firebase_storage.Reference firebaseDocument =
firebase_storage.FirebaseStorage.instance.ref(
'sample_directory/${basenameWithoutExtension(filePath)}_${DateTime.now().toIso8601String()}.txt');
firebaseDocument
.putFile(File(filePath))
.whenComplete(
() async => complete(await firebaseDocument.getDownloadURL()))
.catchError((e){
//Manage your error here
});
}
The text file that you are uploading to the firestore will contain the userID that you are trying to upload. In this case, the google firestore is acting as a remote blob storage container. It must contain a file, even it where using object storage, where the UserID was metadata to a file, you would still have to collect the entire file, then add the metadata, and upload it again as one whole object.
I found the solution
If someone has same problem , i hope it'll help
as you can see i used final that's why 😇
man... i wasted my 3hours

Save network images in Flutter to load them offline

How can I make a network image being available offline in my app? So, when I don't have network, it can be loaded from cache next time.
I'd tried network_to_file_image package and cache network image package as well, but it didn't work for me.
When I used cached network and I opened the app next time (without network) the images didn't load again from cache memory.
Here is the code where I use network image package in my Flutter app:
CarouselSlider(
enlargeCenterPage: true,
autoPlay: true,
height: 350,
initialPage: 0,
items: keyPlans.map((key) {
File file;
getData() {
DirServices().file("loacliamge_${counter++}").then((File data) {
file = data;
});
return file;
}
return new CarousalCard(
image: NetworkToFileImage(file: getData(), url: key, debug: true),
onPressed: () {
setState(() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageView(
image: CachedNetworkImageProvider(key),
text: appTitle,
)));
});
},
);
}).toList());
network_to_file_image package always fetch data from the network.
Temp solution for cache network image package.
Add IOFileSystem class to your project, as described here. (Hope this class will be available from package and we'll be able to remove this duplicated class in future)
import 'package:file/local.dart';
import 'package:flutter_cache_manager/src/storage/file_system/file_system.dart' as c;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
class IOFileSystem implements c.FileSystem {
final Future<Directory> _fileDir;
IOFileSystem(String key) : _fileDir = createDirectory(key);
static Future<Directory> createDirectory(String key) async {
// use documents directory instead of temp
var baseDir = await getApplicationDocumentsDirectory();
var path = p.join(baseDir.path, key);
var fs = const LocalFileSystem();
var directory = fs.directory((path));
await directory.create(recursive: true);
return directory;
}
#override
Future<File> createFile(String name) async {
assert(name != null);
return (await _fileDir).childFile(name);
}
}
And then create CustomCacheManager
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class CustomCacheManager extends CacheManager with ImageCacheManager {
static const String key = "customCache";
static CustomCacheManager _instance;
factory CustomCacheManager() {
return _instance ??= CustomCacheManager._();
}
CustomCacheManager._()
: super(Config(key, fileSystem: IOFileSystem(key)),);
}
Then finally provide this CustomCacheManager to the CachedNetworkImage
CachedNetworkImage(
cacheManager: CustomCacheManager(),
imageUrl: "your_image_url",
)
Then cached files won't be deleted by system between sessions

Resources