DEV Community

Cover image for Upload Files Using React Native and Firebase (Part 4)
Younes Henni
Younes Henni

Posted on • Edited on

Upload Files Using React Native and Firebase (Part 4)

Overview

In the previous parts, you learned how to set up a Firebase Storage service and wrote custom rules for your storage bucket. You also learned how to use Image Picker to upload photos locally and use the storage API to save photos to your bucket.

In this last part of the series, I’ll show you the following.

  • How to monitor the upload progress of your photo.

  • Get the download URL to display the photo from storage.

  • Render a progress bar and a skeleton placeholder while waiting to fetch the photo from Firebase Storage.

You can find the full code in Github.

Let us jump into it.

1. Monitor upload progress

As a reminder, here is the full code we wrote in part 3 of the series in UploafFile/index.js.

import React, { useState } from 'react';
import { Button, StatusBar } from 'react-native';

import ImagePicker from 'react-native-image-picker';

import { imagePickerOptions, uploadFileToFireBase } from '../../utils';
import { Container, Picture, Skeleton, ProgressBar } from '../../styles';

const UploadFile = () => {
  const [imageURI, setImageURI] = useState(null);


  const uploadFile = () => {
    ImagePicker.launchImageLibrary(imagePickerOptions, imagePickerResponse => {
      const { didCancel, error } = imagePickerResponse;
      if (didCancel) {
        alert('Post canceled');
      } else if (error) {
        alert('An error occurred: ', error);
      } else {
        setImageURI({ uri: downloadURL });
        Promise.resolve(uploadFileToFireBase(imagePickerResponse));
      }
    });
  };

  return (
    <Container>
      <StatusBar barStyle="dark-content" />
      <Button title="New Post" onPress={uploadFile} color="green" />
      {imageURI && <Picture source={imageURI} />}
    </Container>
  );
};

export default UploadFile;
Enter fullscreen mode Exit fullscreen mode

Make the following changes to the uploadFile function.

const uploadFile = () => {
    ImagePicker.launchImageLibrary(imagePickerOptions, imagePickerResponse => {
      const { didCancel, error } = imagePickerResponse;
      if (didCancel) {
        alert('Post canceled');
      } else if (error) {
        alert('An error occurred: ', error);
      } else {
        /*
          Remove these two lines
          setImageURI({ uri: downloadURL });
          Promise.resolve(uploadFileToFireBase(imagePickerResponse)); 
          Replace them with these two lines instead
        */ 
        const uploadTask = uploadFileToFireBase(imagePickerResponse);
        monitorFileUpload(uploadTask);
      }
    });
  };
Enter fullscreen mode Exit fullscreen mode

You no longer need to resolve the promise nor set the local state for the image URI. These two steps will be outsourced to a function called monitorFileUpload that you’re going to write shortly.

You’re now saving the results of uploadFileToFirebase in a variable called uploadTask and passing it as a parameter to monitorFileUpload.

Add the following code just on top of the uploadFile function.

const monitorFileUpload = uploadTask => {
  uploadTask.on('state_changed', snapshot => {
    switch (snapshot.state) {
      case 'running':
        setImageURI(null);
        break;
      case 'success':
        snapshot.ref.getDownloadURL().then(downloadURL => {
          setImageURI({ uri: downloadURL });
         });
         break;
      default:
        break;
    }
  });
};

const uploadFile = () => // ..
Enter fullscreen mode Exit fullscreen mode

The above function takes uploadTask as the argument and uses an observer method on('state_changed', callback) to track state changes.

The observer takes two arguments. The first argument is a string parameter, 'state_changed', and the second argument is a callback with a snapshot parameter.

You can find more info on tracking upload progress in the Firebase official docs here.

With a switch statement, we check snapshot.state for its different cases (i.e., 'running', 'success') and update our logic accordingly.

In the case of snapshot.state returns a success message, we use snapshot.ref.getDownloadURL() to get the remote URL of the uploaded file. We then set the local state to that URL.

Time to test the app. Refresh your simulator, and add a new post. After waiting for a while (until the photo is uploaded and the remote URL is created), you should see the photo displayed on the screen.

2. Build the progress bar and a skeleton placeholder

As a best practice, you want to show users a progress bar while waiting for the photo to be fetched from storage. To do this, I’ll show you how to leverage the task.on() observer function to build a progress bar for your app.

Start by adding the following function in utils/index.js.

export const uploadProgress = ratio => Math.round(ratio * 100);
Enter fullscreen mode Exit fullscreen mode

The above function takes a ratio parameter then returns a rounded percentage.

Add uploadProgress to the imports in UploadFile/index.js.

import {
  imagePickerOptions,
  uploadFileToFireBase,
  uploadProgress,
} from '../../utils';
Enter fullscreen mode Exit fullscreen mode

At this point, you need two things.

  • Set the value of the upload progress using the local state.

  • Toggle the progress bar with the placeholder when the photo is ready for display.

Add the following code for the local state inside the UploadFile component.

// Add this
const [upload, setUpload] = useState({
  loading: false,
  progress: 0,
});

const [imageURI, setImageURI] = useState(null);
Enter fullscreen mode Exit fullscreen mode

Update monitorFileUpload with the following code.

const monitorFileUpload = task => {
  task.on('state_changed', snapshot => {

    // Get the upload progress
    const progress = uploadProgress(
      snapshot.bytesTransferred / snapshot.totalBytes
    );

    switch (snapshot.state) {
      case 'running':
        setImageURI(null);

        // Set upload state to true and save progress into local state
        setUpload({ loading: true, progress });

        break;
      case 'success':
        snapshot.ref.getDownloadURL().then(downloadURL => {
          setImageURI({ uri: downloadURL });

          // Set upload state to false
          setUpload({ loading: false });

        });
        break;
      default:
        break;
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

As you see above, we can access the bytesTransferred and totalBytes through the snapshot parameter.

We pass the ratio snapshot.bytesTransferred / snapshot.totalBytes to the uploadProgress defined in utils/index.js to get the percentage of the upload progress.

In case the upload is still running, we set loading to true and we save progress to the local state. When the upload is successful, we set loading to false.

Add the following code inside the return() statement.

return (
  <Container>
    <StatusBar barStyle="dark-content" />
    <Button title="New Post" onPress={uploadFile} color="green" />
    {imageURI && <Picture source={imageURI} />}

    {upload.loading && (
      <>
        <Skeleton />
        <ProgressBar bar={upload.progress} />
      </>
    )}

  </Container>
);
Enter fullscreen mode Exit fullscreen mode

Whenever upload.loading is true, we display a Skeleton component and a ProgressBar component (to be defined shortly).

Notice that ProgressBar takes the props bar={upload.progress} to be used to set the width of the bar.

Let us define the Skeleton and ProgressBar styled-components. Add the following code in styles/index.js.

// ..

export const ProgressBar = styled.View`
  background-color: #039ae5;
  height: 3;
  width: ${props => props.bar}%;
  align-items: flex-start;
`;

export const Skeleton = styled.View`
  height: 300;
  width: 100%;
  background-color: #ebebeb;
`;
Enter fullscreen mode Exit fullscreen mode

Notice that the width of ProgressBar is rendered dynamically with the bar props you defined earlier.

Import these two new components in UploadFile/index.js.

import { Container, Picture, Skeleton, ProgressBar } from '../../styles';
Enter fullscreen mode Exit fullscreen mode

The full code in UploadFile/index.js should look like this.

import React, { useState } from 'react';
import { Button, StatusBar } from 'react-native';

import ImagePicker from 'react-native-image-picker';

import {
  imagePickerOptions,
  uploadFileToFireBase,
  uploadProgress,
} from '../../utils';
import { Container, Picture, Skeleton, ProgressBar } from '../../styles';

const UploadFile = () => {
  const [upload, setUpload] = useState({
    loading: false,
    progress: 0,
  });
  const [imageURI, setImageURI] = useState(null);

  const monitorFileUpload = task => {
    task.on('state_changed', snapshot => {
      const progress = uploadProgress(
        snapshot.bytesTransferred / snapshot.totalBytes
      );
      switch (snapshot.state) {
        case 'running':
          setImageURI(null);
          setUpload({ loading: true, progress });
          break;
        case 'success':
          snapshot.ref.getDownloadURL().then(downloadURL => {
            setImageURI({ uri: downloadURL });
            setUpload({ loading: false });
          });
          break;
        default:
          break;
      }
    });
  };

  const uploadFile = () => {
    ImagePicker.launchImageLibrary(imagePickerOptions, imagePickerResponse => {
      const { didCancel, error } = imagePickerResponse;
      if (didCancel) {
        alert('Post canceled');
      } else if (error) {
        alert('An error occurred: ', error);
      } else {
        const uploadTask = uploadFileToFireBase(imagePickerResponse);
        monitorFileUpload(uploadTask);
      }
    });
  };

  return (
    <Container>
      <StatusBar barStyle="dark-content" />
      <Button title="New Post" onPress={uploadFile} color="green" />
      {imageURI && <Picture source={imageURI} />}
      {upload.loading && (
        <>
          <Skeleton />
          <ProgressBar bar={upload.progress} />
        </>
      )}
    </Container>
  );
};

export default UploadFile;
Enter fullscreen mode Exit fullscreen mode

Time to test your app. Launch or refresh your simulator, and add a new photo.

As you can see, a skeleton placeholder with a blue progress bar is shown while the photo is uploaded to storage.

Conclusion

Congratulations on completing this series of tutorials.

You learned how to use react-native-image-picker to upload photos from your mobile device to Firebase Storage. You then learned how to track the upload progress and display a skeleton placeholder with a progress bar. When the upload is successful, you learned how to fetch the photo from its remote URL and displayed it on the screen.

I hope you enjoyed it. Take care, and see you in the next one.

Top comments (0)