Flutter Web Upload to Firestore

2,541

Solution 1

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.

Solution 2

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

Solution 3

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.

  1. Import image_picker_web. I am using version 2.0.3.
  2. Use ImagePickerWeb.getImageInfo on a button ontap listener to get the image info.
var fileInfo = await ImagePickerWeb.getImageInfo;
  1. Show the image using Image.memory in the widget tree. (optional)
Image.memory(fileInfo.data!,width: 180),
  1. Create firebase upload location
final firebasefileLocation = firebaseStorageLocation.child('${DateTime.now()}_${fireInfo.fileName}');
  1. 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,
        )
      ],
    );
  }
}
Share:
2,541
JobsyNZ
Author by

JobsyNZ

Updated on December 18, 2022

Comments

  • JobsyNZ
    JobsyNZ over 1 year

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