Flutter Web: Firebase Storage
Tags: flutter, flutterweb, webdev, firebase
Published: 2020-01-10 07:26:06

# 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:

```dart
// 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](https://stackoverflow.com/questions/13298140/how-to-upload-a-file-in-dart).

Thus, we have to create an input element. If we were to refer to the docs about file reader for web in [Mozilla doc](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL)

It actually gives an example of what we needed:

```js
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:

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

```dart
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:

```dart
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:

1. User clicks on the button to upload file
2. The window pops up as input is automatically clicked
3. Once user has chosen a file, the FileReader will take some micro seconds to read the file
4. After reading the file, the file will be passed in the `uploadToFirebase()` function
5. We will update the the UploadTask instance to the UploadTask instance we just newly created
6. 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:

```dart
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**.

{% youtube IN3NmDs_U_Y %}

[Github Gist of the firebase storage implementation](https://gist.github.com/happyharis/d7a4a89bbac114af00f921f6c26ab728)

Thank you for reading. Hope you have a nice day 😊