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
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;
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
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;
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>
);
}
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)
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.
for those who wonder I just edited this
const Name = "file-" + new Date().toISOString();
const storageRef = firebase.storage().ref(
files/${Name}
);