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;
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);
}
});
};
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 = () => // ..
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);
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';
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);
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;
}
});
};
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>
);
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;
`;
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';
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;
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)