Using React Native you can build a variety of app screens that are cross-platform using JavaScript as the main programming language. One such app screen feature is uploading photos which is quite a common feature in social media apps. Uploading photos to Firebase Storage is a common practice in React Native apps that have backend integration with Firebase, such as our React Native templates.

In this tutorial, let’s build a demo app in which you are going to create a simple screen that allows you to pick an image from the app running on the device, using an image picker and upload it to the Firebase Cloud Storage. The second screen is going to display these images.

Getting started

Start by creating a new react native project. Run the following command from a terminal window. After the project directory is created, navigate inside it and install the required dependencies.

npx react-native init uploadStorageDemo

cd uploadStorageDemo

yarn add react-native-progress react-native-image-picker

Do note that this tutorial uses a react-native version above 0.60.x. If you are using a version below that, make sure to seek guidance on how to link native binaries for the libraries mentioned in this tutorial.

To follow instructions on how to configure react-native-image-picker for each mobile platform, I highly recommend you to go through the official docs here.

For iOS, make sure to install pods.

cd ios/ && pod install

# after pods install
cd ..

Create an upload screen

The current demo app is going to contain one screen that will help the user to select an image from the device’s photo library. Create a file called UploadScreen.js inside src/screens/ directory and the following mock snippet for now.

import * as React from 'react';
import { Text, View } from 'react-native';

export default function UploadScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Upload!</Text>
    </View>
  );
}

Open App.js file and import the AppTabs.

import React from 'react';
import { StatusBar } from 'react-native';
import UploadScreen from './src/screens/UploadScreen';

const App = () => {
  return (
    <>
      <StatusBar barStyle="dark-content" />
      <UploadScreen />
    </>
  );
};

export default App;

Now, go back to the terminal window and build the react native app for the platform or the OS you wish to run it on.

# for iOS
npx react-native run-ios

# for Android
npx react-native run-android

I am going to use the iOS simulator for this demo. Open the app to the simulator and you are going to see the following output.

Create a new Firebase Project

To access the Firebase credentials for each mobile OS platform and configure them to use the Firebase SDK, create a new Firebase project from the Firebase console, or if you already have access to a project in your console, you can skip this step.

Create a new project as shown below.

Add the details of your Firebase project.

Click the button “Create project”, and you should be redirected to the dashboard screen. You should see your newly-created project on that dashboard.

Add Firebase SDK to React Native app

Using react-native-firebase version 5 or below, since it was a monorepo, all Firebase dependencies were available from a single module to use in a React Native app. However, with version 6 you have to install dependencies based on Firebase features that you want to use. For example, in the current app, to use storage, you are going to install the core app package as well as storage package.

As said that the core module @react-native-firebase/app is always required. Open a terminal window to install these dependencies.

yarn add @react-native-firebase/app @react-native-firebase/storage

Add Firebase credentials to your iOS app

Firebase provides a GoogleService-Info.plist file that contains all the API keys as well as other credentials needed for iOS devices to authenticate the correct Firebase project.

To access these credentials, go to back to the “Firebase console”, and from the dashboard screen of your Firebase project, open “Project settings” from the side menu.

Go to the “Your apps” section and click on the icon iOS to select the platform.

Enter the application details and click on the “Register app”. Then download the GoogleService-Info.plist file, as shown below.

Open Xcode, and then open the /ios/uploadStorageDemo.xcodeproj file. Right-click on the project name and choose the Add Files option—then select the appropriate file to add to this project.

Next, open ios/uploadStorageDemo/AppDelegate.m and add the following header.

#import <Firebase.h>

Within the didFinishLaunchingWithOptions method, add the following configure method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    if ([FIRApp defaultApp] == nil) {
      [FIRApp configure];
    }

Go back to the terminal window to install pods.

cd ios/ && pod install

# after pods are installed
cd ..

Make sure you build the iOS app before running it. Open

npx react-native run-ios

Add Firebase credentials to your Android app

For Android apps, Firebase provides a google-services.json file that contains all the API keys as well as other credentials needed for Android devices to authenticate the correct Firebase project.

Go to the “Your apps” section and click on the icon Android to select the platform.

Download the google-services.json file.

Now copy the downloaded JSON file in React Native project at the following location: /android/app/google-services.json. Open your android/build.gradle file to add the following snippet.

dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.2.0'
    }

Next, open android/app/build.gradle file, and at the very bottom of this file, add the following snippet.

apply plugin: 'com.google.gms.google-services'

Lastly, make sure you build the Android app.

npx react-native run-android

Using React Native Image Picker

In this section, let us start building the app. Start by opening the file UploadScreen.js and import the following statements.

import React, { useState } from 'react';
import {
  View,
  SafeAreaView,
  Text,
  TouchableOpacity,
  StyleSheet,
  Platform,
  Alert,
  Image
} from 'react-native';
import ImagePicker from 'react-native-image-picker';
import storage from '@react-native-firebase/storage';
import * as Progress from 'react-native-progress';

Inside the function component UploadScreen create three state variables. The first, image is going to be used to store the URI of the source of the image. The same URI will be then used to display the image picked by the user and to upload the image to the Firebase cloud storage.

The second state variable is uploading that is going to be false default. This is going to keep track of whether the image is uploading to the cloud storage or not.

The third variable transferred is going to track the progress of the image being upload.

export default function UploadScreen() {
  const [image, setImage] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [transferred, setTransferred] = useState(0);

  //...
}

Add a helper method called selectImage that is going to use the react-native-image-picker to select an image from the device’s library and display the image picker itself. Also, define an options object to set properties like maximum width and height as well as a default path.

This options object is going to be passed as the first parameter in ImagePicker.showPicker() method that is going to return a callback which sends the response object. Using this callback, the path of the image state variable can be set.

You can find the complete set of options to pass in the official docs here.

const selectImage = () => {
  const options = {
    maxWidth: 2000,
    maxHeight: 2000,
    storageOptions: {
      skipBackup: true,
      path: 'images'
    }
  };
  ImagePicker.showImagePicker(options, response => {
    if (response.didCancel) {
      console.log('User cancelled image picker');
    } else if (response.error) {
      console.log('ImagePicker Error: ', response.error);
    } else if (response.customButton) {
      console.log('User tapped custom button: ', response.customButton);
    } else {
      const source = { uri: response.uri };
      console.log(source);
      setImage(source);
    }
  });
};

Define another helper method called uploadImage that is going to upload the image to the cloud storage. This method is going to be asynchronous by default so let us async-await syntax.

Also, when this method triggers, update the value of uploading to true and transferred to 0 to track the progress of the image being upload to the storage.

Using storage from Firebase you can trigger the image upload. It is important to note that the filename has to be passed as a reference as well as the image URI using putFile in the order described below.

After the image has uploaded to the storage, display an alert method using react native component Alert and set state variables to default as shown below.

const uploadImage = async () => {
  const { uri } = image;
  const filename = uri.substring(uri.lastIndexOf('/') + 1);
  const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;

  setUploading(true);
  setTransferred(0);

  const task = storage()
    .ref(filename)
    .putFile(uploadUri);

  // set progress state
  task.on('state_changed', snapshot => {
    setTransferred(
      Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 10000
    );
  });

  try {
    await task;
  } catch (e) {
    console.error(e);
  }

  setUploading(false);

  Alert.alert(
    'Photo uploaded!',
    'Your photo has been uploaded to Firebase Cloud Storage!'
  );

  setImage(null);
};

Here is the complete JSX returned from this functional component. The progress that you are going to show in the app is going to be in the form of a bar.

export default function UploadScreen() {
  //... rest of the code

  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity style={styles.selectButton} onPress={selectImage}>
        <Text style={styles.buttonText}>Pick an image</Text>
      </TouchableOpacity>
      <View style={styles.imageContainer}>
        {image !== null ? (
          <Image source={{ uri: image.uri }} style={styles.imageBox} />
        ) : null}
        {uploading ? (
          <View style={styles.progressBarContainer}>
            <Progress.Bar progress={transferred} width={300} />
          </View>
        ) : (
          <TouchableOpacity style={styles.uploadButton} onPress={uploadImage}>
            <Text style={styles.buttonText}>Upload image</Text>
          </TouchableOpacity>
        )}
      </View>
    </SafeAreaView>
  );
}

Here are the complete styles for the above component.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#bbded6'
  },
  selectButton: {
    borderRadius: 5,
    width: 150,
    height: 50,
    backgroundColor: '#8ac6d1',
    alignItems: 'center',
    justifyContent: 'center'
  },
  uploadButton: {
    borderRadius: 5,
    width: 150,
    height: 50,
    backgroundColor: '#ffb6b9',
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 20
  },
  buttonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold'
  },
  imageContainer: {
    marginTop: 30,
    marginBottom: 50,
    alignItems: 'center'
  },
  progressBarContainer: {
    marginTop: 20
  },
  imageBox: {
    width: 300,
    height: 300
  }
});

Here is the output you are going to get.

To verify that the image is stored in the cloud storage, go back to the Firebase console dashboard and go to the storage section.

Conclusion

Thanks for following up this tutorial. Using react-native-firebase version 6 brings benefits like less configuration and focus more on developing the app.

Do refer the docs of react-native-progress for more information on customizing the progress bar.

You can also check this interesting website on cloud storage reviews.

Next Steps


1 Comment

BAW · June 3, 2020 at 9:33 am

Thanks so much! Most concise tutorial i’ve found! Worked a treat!

Leave a Reply

Your email address will not be published. Required fields are marked *

Shopping Cart