Uploading to Firebase Storage with React Native

By Jo E.

I had the chance to work with React Native lately and tried using it with Firebase, and so far I bumped into just a little bit of 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. We 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.

Photo by Hack Capital on Unsplash

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 open the system UI and let users pick a photo. 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 import 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. 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.

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 .blob() method of the response . . . 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 error because the Fetch API strictly accepts the https:// protocol only—although it is fixable, it is a whole article on its own — but 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.

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

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

Make sure initialize Firebase with firebase.initializeApp(config) somewhere in your project. After we import firebase, we then create a new 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 method that uses the FirebaseStorage.put(), you can visit the Firebase Docs for more info about it. The only thing special we did here that isn’t in the Firebase Docs is call blob.close(); release our BLOB.

By now, our component should look something like this:

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!