DEV Community

Cover image for Upload Image with Expo and Firebase Cloud Storage
Fiston Emmanuel
Fiston Emmanuel

Posted on

Upload Image with Expo and Firebase Cloud Storage

User-uploaded images are common in mobile apps, how can be done with Expo and Firebase cloud storage? - let's find out the solution.

Request a device media library files access permission

Both iOS and Android require a user to grant access to the app before reading or writing to the media library.

Throughout this article, we will use expo-image-picker to request permission and upload an image from the media library.


// hasMediaLibraryPermissionGranted.js

import * as ImagePicker from "expo-image-picker";

const hasMediaLibraryPermissionGranted = async () => {
  let granted =  false;

  const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();

  if (!permission.canAskAgain || permission.status === "denied") {
    granted = false;

  }

  if (permission.granted) {
    granted = true;
  }

  return granted;
};

export default hasMediaLibraryPermissionGranted

Enter fullscreen mode Exit fullscreen mode

Upload image from device media library

expo-image-picker exposes ImagePicker.launchImageLibraryAsync method to select image or video and copy to app cache then return file Universal Resource Identifier (URI) path.

// uploadImageFromDevice.js

const uploadImageFromDevice = async () => {
  let imgURI = null;
  const storagePermissionGranted = await hasMediaLibraryPermissionGranted();

  // Discard execution when  media library permission denied
  if (!storagePermissionGranted) return imgURI;

  let result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [4, 4],
    quality: 1,
  });

  if (!result.cancelled) {
    imgURI = result.uri;
  }

  return imgURI;
};

export default uploadImageFromDevice;


Enter fullscreen mode Exit fullscreen mode

Fetch uploadable image binary data.

The URI return by ImagePicker.launchImageLibraryAsync is a reference to the cached image and doesn't hold the image data.

Most cloud storage providers including Firebase Cloud Storage support a binary large object (BLOB) for uploading various files types.

Let us fetch BLOB data from an image URI .


// getBlobFroUri.js

const getBlobFroUri = async (uri) => {
  const blob = await new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.onload = function () {
      resolve(xhr.response);
    };
    xhr.onerror = function (e) {
      reject(new TypeError("Network request failed"));
    };
    xhr.responseType = "blob";
    xhr.open("GET", uri, true);
    xhr.send(null);
  });

  return blob;
};



export default getBlobFroUri

Enter fullscreen mode Exit fullscreen mode

Upload Binary data to Firebase Cloud Storage

Image BLOB data are ready now, Firebase provides a cloud object storage service with the free-tier plan for an early-stage app.


// manageFileUpload.js


import firebase from "firebase";

const manageFileUpload = async (
  fileBlob,
  { onStart, onProgress, onComplete, onFail }
) => {
  const imgName = "img-" + new Date().getTime();

  const storageRef = firebase.storage().ref(`images/${imgName}.jpg`);

  console.log("uploading file", imgName);

  // Create file metadata including the content type
  const metadata = {
    contentType: "image/jpeg",
  };

  // Trigger file upload start event
  onStart && onStart();
  const uploadTask = storageRef.put(fileBlob, metadata);
  // Listen for state changes, errors, and completion of the upload.
  uploadTask.on(
    "state_changed",
    (snapshot) => {
      // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
      const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;

      // Monitor uploading progress
      onProgress && onProgress(Math.fround(progress).toFixed(2));
    },
    (error) => {
      // Something went wrong - dispatch onFail event with error  response
      onFail && onFail(error);
    },
    () => {
      // Upload completed successfully, now we can get the download URL

      uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
        // dispatch on complete event
        onComplete && onComplete(downloadURL);

        console.log("File available at", downloadURL);
      });
    }
  );
};

export default manageFileUpload;

Enter fullscreen mode Exit fullscreen mode

Finally, Connect all pieces together

//App.js

import * as React from "react";
import {
  Text,
  View,
  StyleSheet,
  Image,
  Platform,
  useWindowDimensions,
} from "react-native";
import Constants from "expo-constants";
import firebase from "./config/firebase";
import { AntDesign, Feather } from "@expo/vector-icons";
import uplodImageFromDevice from "./uploadImageFromDevice";
import getBlobFromUri from "./getBlobFromUri";
import manageFileUpload from "./manageFileUpload";
export default function App() {
  const [imgURI, setImageURI] = React.useState(null);

  const [isUploading, setIsUploading] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [remoteURL, setRemoteURL] = React.useState("");

  const [error, setError] = React.useState(null);
  const { width } = useWindowDimensions();

  const handleLocalImageUpload = async () => {
    const fileURI = await uplodImageFromDevice();

    if (fileURI) {
      setImageURI(fileURI);
    }
  };

  const onStart = () => {
    setIsUploading(true);
  };

  const onProgress = (progress) => {
    setProgress(progress);
  };
  const onComplete = (fileUrl) => {
    setRemoteURL(fileUrl);
    setIsUploading(false);
    setImageURI(null);
  };

  const onFail = (error) => {
    setError(error);
    setIsUploading(false);
  };
  const handleCloudImageUpload = async () => {
    if (!imgURI) return;

    let fileToUpload = null;

    const blob = await getBlobFromUri(imgURI);

    await manageFileUpload(blob, { onStart, onProgress, onComplete, onFail });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.heading}>Attach and upload image</Text>
      {Boolean(imgURI) && (
        <View>
          <Image
            source={{ uri: imgURI }}
            resizeMode="contain"
            style={{ width, height: width }}
          />
        </View>
      )}

      {!isUploading && (
        <View style={styles.row}>
          <AntDesign
            name="addfile"
            size={36}
            color={imgURI ? "green" : "black"}
            onPress={handleLocalImageUpload}
          />

          {Boolean(imgURI) && (
            <Feather
              name="upload-cloud"
              size={36}
              color="black"
              onPress={handleCloudImageUpload}
            />
          )}
        </View>
      )}

      {isUploading && (
        <View style={styles.uploadProgressContainer}>
          <Text> Upload {progress} of 100% </Text>
        </View>
      )}

      {Boolean(remoteURL) && (
        <View style={{ paddingVertical: 20 }}>
          <Text>
            Image has been uploaded at
            <Text style={{ color: "blue" }}> {remoteURL} </Text>
          </Text>
        </View>
      )}
    </View>
  );
}

Enter fullscreen mode Exit fullscreen mode

Expo Snack preview - https://snack.expo.io/@expofire/upload-image-with-expo-and-firebase-cloud-storage

Stuck with code and need 1:1 assistance. Ping me

Top comments (2)

Collapse
 
nonsense96 profile image
NonSense96

big thankss!!!!, firebase version is at 9v plus is not working as it expected, I had to downgrade to 8.3. How can I upload any kind of document? with no extension specified.

Collapse
 
nonsense96 profile image
NonSense96

for those who wonder I just edited this

const Name = "file-" + new Date().toISOString();
const storageRef = firebase.storage().ref(files/${Name});