Uploading to Firebase Storage with React Native

Jo Espina
5 min readMay 21, 2019

I had the chance to work with React Native lately and tried using it with Firebase, and so far I bumped into just a few issues.

One of the issues I found with a React Native + Firebase stack is uploading a file to a Firebase Storage bucket, which is what I will be talking about in this article. I will cover both the Problem and the Solution I found after long hours of trial and error and searching for an answer.

This article assumes that you are using React Native with a Managed Expo Workflow. More about Managed and Bare Workflows https://docs.expo.io/versions/v32.0.0/introduction/managed-vs-bare/

Let’s get started.

Step 1: Getting a File URI

The first thing we need to do is to find a way to get a File URI, the File URI helps us to find and access a particular resource — like a Photo — in a user’s device.

To get a File URI with React Native, we can use the DocumentPicker and the ImagePicker of the Expo SDK. We will be using the ImagePicker in our examples later.

The ImagePicker has two methods: launchImageLibraryAsync() and launchCameraAsync(). The names says it all:

launchImageLibraryAsync() — Displays the system UI for choosing an image or a video from the phone’s library. We will be using this in our examples later.

launchCameraAsync() — Display the system UI for taking a photo with the camera.

I think it’s about time we get to some coding. First of let’s start with a basic React Native component:

import React, { Component } from ‘react’;
import {View, Text, StyleSheet} from ‘react-native’;
const styles = StyleSheet.create({
button: {
padding: 10,
borderWidth: 1,
borderColor: “#333”,
textAlign: “center”,
maxWidth: 150
}
});
class FirebaseStorageUploader extends Component { render () {
var button = <View style={[styles.button]}>
<Text>Choose Photo</Text>
</View>
return (button);
}
} export default FirebaseStorageUploader;

Our component doesn’t do much (Yet!) and simply renders a custom button. We’ll use this button to let users pick a photo with the system UI. To do this we need to handle On User Press events by adding the onPress prop to our button like so:

handleOnPress = () => { 
console.log(“button pressed”);
}
render () {
var button = <View
style={[styles.button]}
onPress={this.handleOnPress}
>
<Text>Choose Photo</Text>
</View>
return (button);
}

Now that we have our onPress event, let’s add ImagePicker to our code and update handleOnPress to use ImagePicker.

import { ImagePicker } from ‘expo’;handleOnPress = () => {   console.log(“button pressed”);   ImagePicker.launchImageLibraryAsync({ 
mediaTypes: “Images”
}).then((result)=>{
if (!result.cancelled) {
// User picked an image
const {height, width, type, uri} = result;
console.log(“image picked”, uri);
}

}).catch((error)=>{
throw error; }); }

Before we proceed, let’s breakdown our new code.

If you haven’t noticed, we imported ImagePicker from expo, not react-native. This is because ImagePicker is part of the Expo SDK.

import { ImagePicker } from ‘expo’;

Next we have ImagePicker.launchImageLibraryAsync. This method accepts an options (object) as an argument. To make our example simple, we only used the options.mediaTypes property, but there are also other properties you can use. Options.mediaTypes helps you choose what type of media users can pick between. The options are: Images, Videos, and All. In our example, we want users to choose only Images.

ImagePicker.launchImageLibraryAsync({ 
mediaTypes: “Images”
})

Once this line is called, the app will then open the System UI for users to pick an image from their Image Library. The code will then return a promise result when the user finishes picking an image or cancels the process. Below is an example promise result:

const { cancelled, uri, width, height, type } = result

There are two important properties I want you to focus on, one of which is the cancelled property. If you look at our code you will see an if condition.

if (!result.cancelled) {

This condition checks the value of the cancelled property. This is because, the result will return { cancelled: true } if the user cancelled the picking process.

The other important property is the uri (URI). This property is important because this is the location or address of the photo the user picked. Later on we will need the URI to create a BLOB of the photo and upload it to Firebase.

Step 2: Creating a blob

One of the easiest ways to create a blob with React Native without using any third-party libraries is to use the Fetch API fetch() and use the result.blob() method of the result . . . BUT . . . its not a straightforward one liner. Although React Native recommends using the Fetch API, you will run into some issues with it, such as a networking errors because the Fetch API strictly accepts the https:// protocol only—although it is fixable, it is a whole article on its own — our uri address uses a file:// protocol. So we will instead use the good old fashion XMLHttpRequest.

uriToBlob = (uri) => {  return new Promise((resolve, reject) => {    const xhr = new XMLHttpRequest();    xhr.onload = function() {
// return the blob
resolve(xhr.response);
};

xhr.onerror = function() {
// something went wrong
reject(new Error('uriToBlob failed'));
};
// this helps us get a blob
xhr.responseType = 'blob';
xhr.open('GET', uri, true);

xhr.send(null);
});}

This promise will help us to convert our URI into a BLOB. Now that we have our BLOB, the only thing left to do is to upload it to Firebase.

Step 3: Uploading BLOB to Firebase

To upload our BLOB to Firebase, we should first import Firebase.

import * as firebase from 'firebase/app';
import 'firebase/storage';

Make sure to initialize Firebase with firebase.initializeApp(config) somewhere in your project. After we import firebase, we will create our last method uploadToFirebase().

uploadToFirebase = (blob) => {  return new Promise((resolve, reject)=>{    var storageRef = firebase.storage().ref();    storageRef.child('uploads/photo.jpg').put(blob, {
contentType: 'image/jpeg'
}).then((snapshot)=>{
blob.close();
resolve(snapshot);
}).catch((error)=>{
reject(error);
});
});}

This is a simple promise that uses the FirebaseStorage.put(), you can visit the Firebase Docs for more info about it. The only thing special we did here is call blob.close(); to release our BLOB.

By now, our component should look something like this:

Wrap up

With our code above you can successfully upload a file to a Firebase Storage with React Native. I did not cover a lot of areas in my example, so make sure to adjust the code to fit your needs, such as handling the “file is uploading” event and the “file upload error/success” event.

I really spent a long time with this issue and have tried many different ways to upload a file to Firebase Storage, including uploading a base64 string to Firebase Storage with FirebaseStorage.putString(), but I didn’t get it to work because Firebase throws an error that the base64 string is not valid, although the base64 string is valid and displays an image in both React Native and a web app. I also tried storing the base64 string in Firestore instead of storage, but it felt wrong for me. So in the end using XMLHttpRequest to generate a blob and storing that blob on Firebase was the best route for my needs.

Credits to @sjchmiel for sharing about using XMLHttpRequest to generate a BLOB.

I hope you liked reading this article, I am planning to write more articles like this one in the near future. Please leave me a message below if you have any questions or if you have alternative ways to upload a file to Firebase Storage with React Native.

Cheers!

--

--

Jo Espina

I am a Fullstack Developer. I love writing code and writing stuff about why my code went wrong. https://www.linkedin.com/in/joananespina/