DEV Community

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

Posted on

4 2 1 1 1

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

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay