How to Monitor Upload and Download Progress for Amplify Storage

Christian Nwamba - Nov 15 '21 - - Dev Community

When you can, tell a user why they are waiting and what they are waiting for, if they have to wait. It’s recommended to manage expectations with your UI. Why most apps don’t have this feature is because it is tricky to implement.

How do you know what is happening on the server that is making a fetch request take so long? Unless the server is updating the client via web socket then you are out of luck.

Amplify Storage has a callback that keeps you updated on the upload and download progress of your file. Here's how to set it up.

Setup a Frontend + Amplify Project

Any frontend set up should work but I’m most familiar with React so I’ll be showing you with a React project. But feel free to create a project in any framework; same principles apply.

npx create-react-app monitoring-storage-progress
Enter fullscreen mode Exit fullscreen mode

To configure Amplify on this project, you need to have the Amplify CLI installed.

npm install -g @aws-amplify/cli
Enter fullscreen mode Exit fullscreen mode

On the newly created project, run the init command to setup Amplify

amplify init
Enter fullscreen mode Exit fullscreen mode

Since we intend to work with just Amplify Storage, run the add command to configure the project with AWS S3:

amplify add storage
Enter fullscreen mode Exit fullscreen mode

Install the Amplify frontend library to your React project:

npm install aws-amplify
Enter fullscreen mode Exit fullscreen mode

Configure the library in index.js before rendering the React tree:

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
Enter fullscreen mode Exit fullscreen mode

Amplify adds an aws-exports file when you set up Amplify in case you were wondering.

Uploading Files

In your src/App.js add an input element of type file:

<div>
  <div>
    <label htmlFor="f">
      <input
        type="file"
        id="f"
        onChange={(e) => {
          uploadFile(e.target.files[0]);
        }}
      />
      <div>Pick a File to Upload</div>
    </label>
  </div>
</div>;
Enter fullscreen mode Exit fullscreen mode

The onChange method calls uploadFile and passes it the file that you picked.

Before you implement uploadFile, import Storage from the Amplify library in the src/App.js file. You need the methods it exposes to upload and download files from AWS S3:

import { Storage } from "aws-amplify";
Enter fullscreen mode Exit fullscreen mode

Now create the uploadFile function in the App component:

const [key, setKey] = React.useState("");

const uploadFile = async (file) => {
  try {
    const result = await Storage.put(file.name, file, {
      contentType: file.type,
    });
    console.log(result);
    setKey(result.key);
  } catch (error) {
    console.log(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

uploadFile forwards the file object to Storage.put which takes the name of the file, the file object and a configuration object. The file name does not have to be the name of the file that you are uploading, you can use any string.

After the upload, Storage.put returns a key which you can use to identify the file that was uploaded. Kinda like the unique ID for the uploaded file. I’ve set the returned key as the value of key state because we need the key to download the uploaded file.

Downloading Files

To download a file, call the Storage.get function and pass it a key to the file you want to download.

Add a button to trigger the download and call a downloadFile function in the onClick event handler:

{key && (
  <button
    onClick={() => {
      downloadFile();
    }}>
    Download
  </button>
)}
Enter fullscreen mode Exit fullscreen mode

Add the downloadFile function in the App component:

const downloadFile = async () => {
  try {
    const data = await Storage.get(key, { download: true });
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

Monitoring Upload and Download Progress

The put and get methods on the Storage object take a config object as one of the arguments:

Storage.put(file.name, file, { /* Config */ });
Storage.get(key, { /* Config */ });
Enter fullscreen mode Exit fullscreen mode

You can pass a function called progressCallback to the object and Amplify will call it with progress data:

const progressCallback = (progress) => {
  console.log(`Progress: ${progress.loaded}/${progress.total}`);
};

Storage.put(file.name, file, { progressCallback });
Storage.get(key, { progressCallback });
Enter fullscreen mode Exit fullscreen mode

I took an extra step to convert the progress data to percentage values:

const progressCallback = (progress) => {
  const progressInPercentage = Math.round(
    (progress.loaded / progress.total) * 100
  );
  console.log(`Progress: ${progressInPercentage}%`);
};

const uploadFile = async (file) => {
  try {
    const result = await Storage.put(file.name, file, {
      contentType: file.type,
      // Progress callback
      progressCallback,
    });
    console.log(result);
    setKey(result.key);
  } catch (error) {
    console.log(error);
  }
};

const downloadFile = async () => {
  try {
    const data = await Storage.get(key, { download: true, progressCallback /*Progress Callback*/ });
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

You can take the demo even one step further and add a progress bar. Progress bars take a current value and a max value. And you have these data already. You can learn more tricks like this form the Storage docs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .