Upload Images to Firebase Storage in React Native
This guide explains the steps to upload a selected image from a device to Firebase storage using the image picker helper functions.
Oct 17, 2020 • 10 Minute Read
Introduction
The Firebase storage NPM module provides access to the Firebase app instance to create a reference object that is used to upload images to the Firebase storage. The Firebase storage bucket can store the data using a file or folder. A reference instance can also take a string path value to upload an image to Firebase storage.
This guide explains the steps to upload a selected image from a device to Firebase storage using the image picker helper functions. Check out these previous guides for more context and complete details:
-
Upload Images to Firebase Storage in React Native (this guide)
Uploading Files to Firebase Storage
The @react-native-firebase/storage module provides access to the FirebaseApp instance that is used to upload media content into the Firebase storage. A storage() instance contains all the necessary details to verify and establish a connection with the Firebase server app to access storage contents.
Follow these steps to upload an image to Firebase storage:
- Import the default Firebase app module.
- Create an instance of the Firebase storage API and use the ref function to get a reference instance to upload an image.
- Invoke the putFile function with a file path/URI to upload the file to Firebase storage.
- The puFile function returns a task object that supports Promise based implementation to track the result of the upload process using the then function.
import storage from '@react-native-firebase/storage'; // 1
uploadImageToStorage(path, imageName) {
let reference = storage().ref(imageName); // 2
let task = reference.putFile(path); // 3
task.then(() => { // 4
console.log('Image uploaded to the bucket!');
}).catch((e) => console.log('uploading image error => ', e));
}
The reference instance also supports different file formats, such as put(Blob | Uint8Array | ArrayBuffer), and provides a putString to upload different types of data.
Image Picker Optimizations
The image picker implementation can be optimized with the Platform.select function. A response object can be used to retrieve platform-specific values and the name of the file:
- Getting a Platform-specific Image Path/URI: Android and iOS use different path/URI schema formats to access the images. iOS uses the file:/// schema in path value, whereas Android uses a plain URI to access images. The different file path or URI values can be handled using the Platform API to verify the OS type at run time:
/**
* Get platform specific value from response
*/
getPlatformPath({ path, uri }) {
return Platform.select({
android: { path },
ios: { uri }
})
}
Note: It's recommended to use platform APIs as any change in API can break our custom logic. Also, the image path should be formatted to support the file:/// protocol.
- Getting an Image Name: The response object provides the name of the file using the response.fileName property, which can be used to store the images with default names instead of custom names.
Display Uploading Status
It is important to inform the user about the image loading process. This can be done using the ActivityIndicator component to show a circular progress indicator on the UI. The progress indicator is shown during the image uploading process. Follow these steps to implement it:
- Create an isLoading property to track the uploading task status.
- Update the value of isLoading to true at the beginning of the uploading task and set it to false inside the then and catch.
- Import ActivityIndicator and display it using the isLoading value:
// App.js
/**
* @author Pavneet Singh
*/
import React from "react";
import {
StyleSheet,
View,
Button,
Image,
ActivityIndicator,
Platform,
SafeAreaView,
Text,
} from "react-native";
import storage from '@react-native-firebase/storage';
import ImagePicker from 'react-native-image-picker';
export default class App extends React.Component {
state = {
imagePath: require("./img/default.jpg"),
isLoading: false,
status: '',
}
chooseFile = () => {
this.setState({ status: '' });
var options = {
title: 'Select Image',
customButtons: [
{ name: 'customOptionKey', title: 'Choose Photo from Custom Option' },
],
storageOptions: {
skipBackup: true, // do not backup to iCloud
path: 'images', // store camera images under Pictures/images for android and Documents/images for iOS
},
};
ImagePicker.showImagePicker(options, response => {
if (response.didCancel) {
console.log('User cancelled image picker', storage());
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
let path = this.getPlatformPath(response).value;
let fileName = this.getFileName(response.fileName, path);
this.setState({ imagePath: path });
this.uploadImageToStorage(path, fileName);
}
});
};
getFileName(name, path) {
if (name != null) { return name; }
if (Platform.OS === "ios") {
path = "~" + path.substring(path.indexOf("/Documents"));
}
return path.split("/").pop();
}
uploadImageToStorage(path, name) {
this.setState({ isLoading: true });
let reference = storage().ref(name);
let task = reference.putFile(path);
task.then(() => {
console.log('Image uploaded to the bucket!');
this.setState({ isLoading: false, status: 'Image uploaded successfully' });
}).catch((e) => {
status = 'Something went wrong';
console.log('uploading image error => ', e);
this.setState({ isLoading: false, status: 'Something went wrong' });
});
}
/**
* Get platform specific value from response
*/
getPlatformPath({ path, uri }) {
return Platform.select({
android: { "value": path },
ios: { "value": uri }
})
}
getPlatformURI(imagePath) {
let imgSource = imagePath;
if (isNaN(imagePath)) {
imgSource = { uri: this.state.imagePath };
if (Platform.OS == 'android') {
imgSource.uri = "file:///" + imgSource.uri;
}
}
return imgSource
}
render() {
let { imagePath } = this.state;
let imgSource = this.getPlatformURI(imagePath)
return (
<SafeAreaView style={styles.container}>
{this.state.isLoading && <ActivityIndicator size="large" style={styles.loadingIndicator} />}
<View style={styles.imgContainer}>
<Text style={styles.boldTextStyle}>{this.state.status}</Text>
<Image style={styles.uploadImage} source={imgSource} />
<View style={styles.eightyWidthStyle} >
<Button title={'Upload Image'} onPress={this.chooseFile}></Button>
</View>
</View>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
height: '100%',
backgroundColor: '#e6e6fa',
},
imgContainer: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
width: '100%',
height: '100%'
},
eightyWidthStyle: {
width: '80%',
margin: 2,
},
uploadImage: {
width: '80%',
height: 300,
},
loadingIndicator: {
zIndex: 5,
width: '100%',
height: '100%',
},
boldTextStyle: {
fontWeight: 'bold',
fontSize: 22,
color: '#5EB0E5',
}
});
Tips
- Validating network availability can help to prompt the user to enable internet connectivity for the image loading task. The react-native-netinfo NPM module is the official way to identify the network status.
- Use the Firebase authentication feature to allow only authenticated users to access data.
- The react-native-image-picker also supports custom buttons with the customButtons option property to fetch images from other platforms or custom social media APIs.
- The Firebase rules can also be modified to allow only public read and authenticated writes:
alternately
allow read;
allow write: if request.auth != null;
Read more about the Firebase rules here.
Conclusion
Firebase is specifically optimized to address issues on mobile devices with many other great services. It is important to integrate Firebase configuration and rules (or authentication services) carefully. React Native offers all the required NPM plugins to integrate Firebase services and required features to fetch images, check network status, and much more. The complete optimized codebase is available on the RNFirebaseStorage repository. Happy coding!