How To Use React Native Expo Image Picker and Firebase File Upload
Tags: reactnative, firebase, expo, mobile
Published: 2023-06-10 21:51:44


##Overview

This video demonstrates a simple [React Native Expo application](https://docs.expo.dev) that integrates [Firebase Storage](https://docs.expo.dev/guides/using-firebase/) and [Expo Image Picker](https://docs.expo.dev/versions/latest/sdk/imagepicker/) to capture and upload images to Firebase Storage.

We also included the [`react-native-dotenv`](https://www.npmjs.com/package/react-native-dotenv) npm package to manage our firebase environment variables.

It showcases the use of Firebase SDK functions, React Hooks, and the Expo Image Picker library to interact with the device's camera and Firebase Storage.
{% youtube tX_HPvwB-5c %}

Below is additional information in the final files that were create to build the application and steps to set it all 

### Create Project and Install npm Packages
```
# create project
npx create-expo-app my-app-camera-fb

#install dotenv for env variables
npm install -D react-native-dotenv

# using camera from image picker
npx expo install expo-image-picker

# using firebase javascript API
npx expo install firebase

# ensuring the firebase api is packaged correctly
npx expo customize metro.config.js
```
### Updates to `metro.config.js`
```javascript
const { getDefaultConfig } = require('@expo/metro-config');

const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push('cjs');

module.exports = defaultConfig;
```

### `.env` File Setup and Configuration
you need to update the `babel.config.js`, see the addition to the `plugins` property
```javascript
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    "plugins": [
      ["module:react-native-dotenv"]
    ]
  };
};
```
you also need to create a `.env` file in the root of you project to hold the `firebase` keys needed for application
```
FIREBASE_API_KEY=""
FIREBASE_AUTH_DOMAIN=""
FIREBASE_DATABASE_URL=
FIREBASE_PROJECT_ID=""
FIREBASE_STORAGE_BUCKET=""
FIREBASE_MESSAGING_SENDER_ID=
FIREBASE_APP_ID=""
```

### Permissions Configuration
In `app.json` you need to set information for ios plist
```json
"plugins": [
      [
        "expo-image-picker",
        {
          "photosPermission": "Allow $(PRODUCT_NAME) to access your photo",
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
        }
      ]
    ]
```

### Firebase Code
This code snippet is an example of how to use Firebase Storage in a JavaScript or TypeScript project. Let's go through it step by step:

The code begins by importing specific variables from the @env package. This package is commonly used to store environment variables securely.
```react
import {
  FIREBASE_API_KEY,
  FIREBASE_STORAGE_BUCKET,
  FIREBASE_APP_ID,
  FIREBASE_PROJECT_ID,
  FIREBASE_AUTH_DOMAIN,
} from "@env"; 
```

Next, it imports various functions and objects from the Firebase App and Firebase Storage modules. These modules are part of the Firebase SDK, which provides tools for building Firebase-powered apps.

```react
import { initializeApp, getApp, getApps } from "firebase/app";
import {
  getStorage,
  ref,
  uploadBytesResumable,
  getDownloadURL,
  listAll,
} from "firebase/storage";
```

The code defines an object called firebaseConfig that contains the configuration options for your Firebase project. These options include the API key, storage bucket, app ID, project ID, and authentication domain. These values are obtained from the Firebase console when setting up a project.

```react
// Initialize Firebase
const firebaseConfig = {
  apiKey: FIREBASE_API_KEY,
  storageBucket: FIREBASE_STORAGE_BUCKET,
  appId: FIREBASE_APP_ID,
  projectId: FIREBASE_PROJECT_ID,
  authDomain: FIREBASE_AUTH_DOMAIN,
};
```

The console.log(firebaseConfig) line outputs the configuration object to the console for debugging purposes.

The if `(getApps().length === 0)` condition checks if there are any Firebase apps initialized. This is to ensure that the Firebase app is only initialized once and prevents errors if this code is executed multiple times.

If no Firebase apps are initialized, the `initializeApp(firebaseConfig)` function is called to initialize the Firebase app using the provided configuration.

```react
if (getApps().length === 0) {
  initializeApp(firebaseConfig);
}
```

The `getApp()` function is then used to retrieve the default Firebase app instance, and `getStorage()` is used to get a reference to the Firebase Storage service. I never actually needed these variables, but it was left in the code to show how they can be exported for use in other parts of application.

The `listFiles` function is defined as an asynchronous function that lists all the files in a specific Firebase Storage reference. It first creates a reference to the desired location using `ref(storage, "images")`, where `"images"` is the path to the directory you want to list files from. It then calls `listAll` on the reference to retrieve a list of files and directories under that path.
```react
const listFiles = async () => {
  const storage = getStorage();

  // Create a reference under which you want to list
  const listRef = ref(storage, "images");

  // Find all the prefixes and items.
  const listResp = await listAll(listRef);
  return listResp.items;
};
```

The `uploadToFirebase` function is defined to upload a file to Firebase Storage. It takes three parameters: uri (the URI of the file to upload), name (the desired name of the uploaded file), and onProgress (an optional callback to track the upload progress). This function fetches the file data from the provided URI, creates a reference to the desired location using `ref(getStorage(), "images/${name}")`, and then uses `uploadBytesResumable` to upload the file in chunks. The function returns a Promise that resolves with the download URL and metadata of the uploaded file.
```react
const uploadToFirebase = async (uri, name, onProgress) => {
  const fetchResponse = await fetch(uri);
  const theBlob = await fetchResponse.blob();

  const imageRef = ref(getStorage(), `images/${name}`);

  const uploadTask = uploadBytesResumable(imageRef, theBlob);

  return new Promise((resolve, reject) => {
    uploadTask.on(
      "state_changed",
      (snapshot) => {
        const progress =
          (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress && onProgress(progress);
      },
      (error) => {
        // Handle unsuccessful uploads
        console.log(error);
        reject(error);
      },
      async () => {
        const downloadUrl = await getDownloadURL(uploadTask.snapshot.ref);
        resolve({
          downloadUrl,
          metadata: uploadTask.snapshot.metadata,
        });
      }
    );
  });
};
```

Finally, the code exports the `fbApp`, `fbStorage`, `uploadToFirebase`, and `listFiles` variables, which can be imported and used in other parts of your application.
```react
export { fbApp, fbStorage, uploadToFirebase, listFiles };
```

Overall, this code initializes the Firebase app and provides functions for listing files in Firebase Storage and uploading files to Firebase Storage.

## Main Application Code
The code imports necessary components and functions from various packages, including `expo-status-bar`, React Native components, `expo-image-picker`, Firebase-related modules (`listFiles` and `uploadToFirebase` functions from `firebase-config.js`), and the custom `MyFilesList` component.

The App function is the main component of the application. It uses React Hooks (useState and useEffect) to manage state and lifecycle events.

We have defined the following code to leverage the `useCameraPermissions` hook to get current permission and also a function to call to prompt the user for permission, we will reference that later in this module.
```react
const [permission, requestPermission] = ImagePicker.useCameraPermissions();
const [files, setFiles] = useState([]);
```

Inside the useEffect hook, the code fetches the initial list of files from Firebase Storage when the component mounts. It calls the `listFiles` function from `firebase-config.js` to retrieve a list of files. It then maps the response to create an array of file objects with just the name property (extracted from the fullPath property of each file). Finally, it updates the state with the array of files using the setFiles function.

The console.log(files) statement logs the current state of the files array to the console for debugging purposes.

The `takePhoto` function is an asynchronous function that handles capturing an image using the device's camera. It utilizes the `launchCameraAsync` function from `expo-image-picker` to open the camera interface. The function passes some configuration options such as allowing editing, capturing all media types, and setting the image quality to 1. Once an image is captured, it checks if the operation was canceled or not.
```react
  const takePhoto = async () => {
    try {
      const cameraResp = await ImagePicker.launchCameraAsync({
        allowsEditing: true,
        mediaTypes: ImagePicker.MediaTypeOptions.All,
        quality: 1,
      });

      if (!cameraResp.canceled) {
        const { uri } = cameraResp.assets[0];
        const fileName = uri.split("/").pop();
        const uploadResp = await uploadToFirebase(uri, fileName, (v) =>
          console.log(v)
        );
        console.log(uploadResp);

        listFiles().then((listResp) => {
          const files = listResp.map((value) => {
            return { name: value.fullPath };
          });

          setFiles(files);
        });
      }
    } catch (e) {
      Alert.alert("Error Uploading Image " + e.message);
    }
  };
```

If the image capture was not canceled, it extracts the URI (file path) of the captured image and obtains the file name by splitting the URI and retrieving the last segment.

It then calls the `uploadToFirebase` function from `firebase-config.js`, passing the `URI`, `file name`, and an optional `callback` function to track the upload progress. The function uploads the image to Firebase Storage using the Firebase SDK. The returned promise resolves with an object containing the `download URL` and `metadata` of the uploaded file. This information is logged to the console for debugging purposes.

After a successful upload, the code fetches the updated list of files from Firebase Storage by calling the `listFiles` function again. It follows the same process as before, mapping the response to create an array of file objects and updating the state with the new array of files.

If any error occurs during the image capture or upload process, an error message is displayed using `Alert.alert` function from React Native.

The user should only be able to take pictures if permission in granted; There are some addition configuration actions required for this to work.
```
```


The code checks if camera permission is granted by comparing the status property of the permission object obtained from `ImagePicker.useCameraPermissions()` with `ImagePicker.PermissionStatus.GRANTED`. If permission is not granted, a view is rendered that displays a message indicating the permission status and a button to request permission.
```react
// permission check
if (permission?.status !== ImagePicker.PermissionStatus.GRANTED) {
    return (
      <View style={styles.container}>
        <Text>Permission Not Granted - {permission?.status}</Text>
        <StatusBar style="auto" />
        <Button title="Request Permission" onPress={requestPermission}></Button>
      </View>
    );
  }
```

If camera permission is granted, the main UI is rendered. It includes a <Text> component displaying a message, the MyFilesList component (which renders the list of files), a <StatusBar> component, and a <Button> component that triggers the takePhoto function when pressed.
```react
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.container}>
        <Text>Working With Firebase and Image Picker</Text>
        <MyFilesList files={files} />
        <StatusBar style="auto" />
        <Button title="Take Picture" onPress={takePhoto}></Button>
      </View>
    </SafeAreaView>
  );
```

### Custom `MyList` Component 

This code defines a custom React Native component called `MyFilesList`. It is used to display a list of files in a flat list format. Here's a breakdown of the code:

The MyFilesList function is the main component. It receives a prop called files, which is an array of file objects.
```react
export default function MyFilesList({ files }) {
```

Inside the `MyFilesList` component, there is a nested component called `Item`. This component represents each item in the list and receives a prop called `name`, which represents the name of the file.

The Item component renders a View component with a custom style (styles.item). Inside the view, there is a Text component that displays the file name using the name prop. The text style is defined by the `styles.title style`.
```react
  const Item = ({ name }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{name}</Text>
    </View>
  );
```

The `MyFilesList` component returns a `FlatList` component from React Native. The `FlatList` is the main component responsible for rendering the list of files.
```react
  return (
    <FlatList
      data={files}
      renderItem={({ item }) => <Item name={item.name} />}
      keyExtractor={(item) => item.name}
    />
  );
```

The FlatList component receives several props:

- `data` is set to the files prop, which represents the array of file objects.
- `renderItem` is a callback function that renders each item in the list. It receives the item object, and in this case, it renders the Item component and passes the name property of the item object as the name prop of the Item component.
- `keyExtractor` is a function that extracts a unique key for each item in the list. In this case, it uses the `name` property of each item as the key.

Finally, the code defines a styles object using StyleSheet.create(). It defines two styles: item and title. The item style sets margin vertical and horizontal spacing, and the title style sets the font size to 18.

This code provides a reusable component MyFilesList that takes an array of files and displays them in a flat list format. It demonstrates the usage of React Native's FlatList component, custom nested components, and styles defined using StyleSheet.create().

### Full Project In GitHub
{% github aaronksaunders/react-native-expo-image-picker-camera-firebase %}