Flutter Web: Firebase Storage
*Disclaimer: The firebase storage api for Flutter Web is still buggy. There is some uncaught errors in the JS end of the framework. Use at your own risk!
Video tutorial and link for the full code is down below ๐
First, we need to create an upload image button with the function name uploadImage:
// Currently, my app is using a Scaffold widget, hence floatingActionButton
floatingActionButton: FloatingActionButton.extended(
heroTag: 'picker',
elevation: 0,
backgroundColor: Colors.tealAccent[400],
hoverElevation: 0,
label: Row(
children: <Widget>[
Icon(Icons.file_upload),
SizedBox(width: 10),
Text('Upload Image')
],
),
onPressed: () => uploadImage(),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
Second, let's create the function uploadImage()
. It's a simple function:
- a. We need to choose a file that we want to upload
- b. Once it finishes reading, we then upload it to firebase storage
Currently, flutter web is still in beta. Thus, we have to use the HTML library. I believe the flutter team does not want to use any html library to be consistent with one codebase for multiplatform
IDK, we will see
I got the help from this stackoverflow question.
Thus, we have to create an input element. If we were to refer to the docs about file reader for web in Mozilla doc
It actually gives an example of what we needed:
function previewFile() {
const preview = document.querySelector("img");
const file = document.querySelector("input[type=file]").files[0];
const reader = new FileReader();
reader.addEventListener(
"load",
function() {
// convert image file to base64 string
preview.src = reader.result;
},
false
);
if (file) {
reader.readAsDataURL(file);
}
}
Thus, we should convert it to dart:
/// A "select file/folder" window will appear. User will have to choose a file.
/// This file will be then read, and uploaded to firebase storage;
uploadImage() async {
// HTML input element
InputElement uploadInput = FileUploadInputElement();
uploadInput.click();
uploadInput.onChange.listen(
(changeEvent) {
final file = uploadInput.files.first;
final reader = FileReader();
// The FileReader object lets web applications asynchronously read the
// contents of files (or raw data buffers) stored on the user's computer,
// using File or Blob objects to specify the file or data to read.
// Source: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
reader.readAsDataUrl(file);
// The readAsDataURL method is used to read the contents of the specified Blob or File.
// When the read operation is finished, the readyState becomes DONE, and the loadend is
// triggered. At that time, the result attribute contains the data as a data: URL representing
// the file's data as a base64 encoded string.
// Source: https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
reader.onLoadEnd.listen(
// After file finiesh reading and loading, it will be uploaded to firebase storage
(loadEndEvent) async {
uploadToFirebase(file);
},
);
},
);
}
Now, we have to create the function uploadToFirebase()
that requires a file.
import 'package:firebase/firebase.dart' as fb;
import 'package:firebase/firestore.dart' as fs;
// In your screen (stateful) widget
// Before your build method, create an UploadTask instance
fb.UploadTask _uploadTask;
/// Upload file to firebase storage and updates [_uploadTask] to the latest
/// file upload
uploadToFirebase(File imageFile) async {
final filePath = 'images/${DateTime.now()}.png';
setState(() {
_uploadTask = fb
.storage()
.refFromURL('YOUR FIREBASE STORAGE URL')
.child(filePath)
.put(imageFile);
});
}
The function above will create a images folder in firebase storage. Moreover, the picture uploaded will be named as time stamps (for reference and simplicity sake).
Lastly, let's create a progress indicator to show the progress of the file being uploaded in a stream builder:
import 'package:firebase/firebase.dart' as fb;
// In your screen widget
StreamBuilder<fb.UploadTaskSnapshot>(
stream: _uploadTask?.onStateChanged,
builder: (context, snapshot) {
final event = snapshot?.data;
// Default as 0
double progressPercent = event != null
? event.bytesTransferred / event.totalBytes * 100
: 0;
if (progressPercent == 100) {
return Text('Successfully uploaded file ๐');
} else if (progressPercent == 0) {
return SizedBox();
} else {
return LinearProgressIndicator(
value: progressPercent,
);
}
},
),
If you have fast internet, the upload speed is very fast. Here's the flow of the upload process:
- User clicks on the button to upload file
- The window pops up as input is automatically clicked
- Once user has chosen a file, the FileReader will take some micro seconds to read the file
- After reading the file, the file will be passed in the
uploadToFirebase()
function - We will update the the UploadTask instance to the UploadTask instance we just newly created
- This will update the progress indicator and show a success message
What is suppose to look like
In the StreamBuilder above, it should look like this:
import 'package:firebase/firebase.dart' as fb;
StreamBuilder<fb.UploadTaskSnapshot>(
stream: _uploadTask?.onStateChanged,
builder: (context, snapshot) {
final event = snapshot?.data;
switch (event?.state) {
case fb.TaskState.RUNNING:
return LinearProgressIndicator(
value: progressPercent,
);
case fb.TaskState.SUCCESS:
return Text('Success ๐');
case fb.TaskState.ERROR:
return Text('Has error ๐ข');
default:
// Show empty when not uploading
return SizedBox();
}
}),
However, there is a bug ๐, where it returns a null error and type check error in the web console, when you inspect in your browser.
Disclaimer again, do at your own risk.
Github Gist of the firebase storage implementation
Thank you for reading. Hope you have a nice day ๐
Top comments (13)
Thank you for the tutorial!
It works!
But I have 2 questions:
1) the upload does work from an android browser but not from iPhone. Do you know why?
2) the images, that I upload from android are rotated in firebase. Do you know why?
Thanks!
I solved the first issue.
The OnChanged does not work with sarafi mobile.
Using Eventlistener is the solution.
Sorry I don't. However, thanks for sharing the first issue's solution!
I am getting below error
TypeError: Cannot read property 'storage' of undefined
at Object.storage$ as storage
at localhost:54783/packages/dharmacha...
at banner._BannerManagerState.new.setState (localhost:54783/packages/flutter/s...)
at banner._BannerManagerState.new.uploadToFirebase (localhost:54783/packages/dharmacha...)
at uploadToFirebase.next ()
at runBody (localhost:54783/dart_sdk.js:37190:34)
at Object._async as async
at banner._BannerManagerState.new.uploadToFirebase (localhost:54783/packages/dharmacha...)
at banner._BannerManagerState.new. (localhost:54783/packages/dharmacha...)
Hi i saw your video. But image doesnt upload
have you set your rules to be able to write without authentications?
same here.
it doesnt make upload.
the rules doesnt require any authentication.
what can it be the reason?
I found the solution by reading comments below YouTube post.
I have to add the following line to index.html as a script.
gstatic.com/firebasejs/7.11.0/fire...
downloadURL? How?
You saved my night, thanks buddy!
Hey, thanks for the article!
Any idea how to go about streaming FireStore data to a list in Flutter web?
Are you getting from a collection or documents?
If collection firestoreData.snapshot.map((data) => data.docs.map((doc)=> doc.data))).toList()
If documents, just add in manually in a list like list.insert(data)
TypeError: dart.global.firebase.storage is not a function
when i press enter i see this error