Author avatar

Pavneet Singh

Upload Images to Firebase Storage in React Native

Pavneet Singh

  • Oct 17, 2020
  • 10 Min read
  • 58 Views
  • Oct 17, 2020
  • 10 Min read
  • 58 Views
Web Development
Front End Web Development
Client-side Frameworks
React

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:

  1. 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:

  1. Import the default Firebase app module.
  2. Create an instance of the Firebase storage API and use the ref function to get a reference instance to upload an image.
  3. Invoke the putFile function with a file path/URI to upload the file to Firebase storage.
  4. The puFile function returns a task object that supports Promise based implementation to track the result of the upload process using the then function.
1
2
3
4
5
6
7
8
9
10
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));
}
JSX

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:
1
2
3
4
5
6
7
8
9
/**
 * Get platform specific value from response
 */
getPlatformPath({ path, uri }) {
    return Platform.select({
        android: { path },
        ios: { uri }
    })
}
JSX

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:

  1. Create an isLoading property to track the uploading task status.
  2. Update the value of isLoading to true at the beginning of the uploading task and set it to false inside the then and catch.
  3. Import ActivityIndicator and display it using the isLoading value:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 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',
    }

});
JSX

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:
1
2
3
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!

0