In this article, I will show you how to save an image from a remote url to your device using react-native.
TL/DR Find the full code in this github repo
I'm assuming you already have node and react-native set up, if you don't, please check out the official docs to learn how to set up a react native project.
The following packages are required for this tutorial
Installing the packages
Install the packages using yarn
yarn add @react-native-community/cameraroll rn-fetch-blob
or npm
npm install @react-native-community/cameraroll rn-fetch-blob --save
Link the native code
if your react-native version >= 0.60, autolink using
cd ios && pod install
else link manually with
react-native link @react-native-community/cameraroll
react-native link rn-fetch-blob
With this, we are done installing the required packages. Navigate to app.js
project_dir -> app.js
delete the default react native code and replace it with this block
// app.js
import React from 'react';
import {SafeAreaView, StyleSheet, StatusBar, Text} from 'react-native';
class App extends React.Component {
render() {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<Text>Save Image</Text>
</SafeAreaView>
</>
);
}
}
export default App;
const styles = StyleSheet.create({
container: {
backgroundColor: '#2FF345CC',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
Run the app in the simulator with
react-native run-ios # or react-native run-android
You should see something like this
Setting up permissions
The user's permission is required for cameraroll to save images to their device. On ios, NSPhotoLibraryUsageDescription
and NSPhotoLibraryAddUsageDescription
keys must be set up in info.plist with a string that describes how your app will use this data. Navigate to info.plist
project_dir -> ios -> project_name -> info.plist
add the following block to info.plist
<!-- info.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app would like to save images to your device.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app would like to save images to your device.</string>
...
</dict>
</plist>
Bear in mind that the description string must be a bit more descriptive than this.
On android, permission is required to write to external storage. Permission can be requested by adding the following block to AndroidManifest.xml
# navigate to AndroidManifest.xml
project_dir -> android -> app -> src -> main -> AndroidManifest.xml
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.saveremoteimage">
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Implementation
This is an image of what the end product would look like
Let's build the UI. First, start by creating a view that would contain all our components
// app.js
...
import {SafeAreaView, StyleSheet, StatusBar, Text, View} from 'react-native';
class App extends React.Component {
render() {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.app}>
<Text>Save Image</Text>
</View>
</SafeAreaView>
</>
);
}
}
...
const styles = StyleSheet.create({
...
app: {
backgroundColor: '#11131B',
flex: 1,
alignSelf: 'stretch',
alignItems: 'center',
paddingVertical: 30,
},
});
Then we add a TextInput. This TextInput is where the url of the image would be entered
// app.js
...
import {
...
TextInput,
} from 'react-native';
class App extends React.Component {
render() {
return (
<>
...
<View style={styles.app}>
<Text style={styles.headerText}>React Native Image Downloader</Text>
<View style={styles.textInputWrapper}>
<TextInput
placeholder="Enter image url here"
style={styles.textInput}
/>
</View>
<Text>Save Image</Text>
</View>
...
</>
);
}
}
...
const styles = StyleSheet.create({
...
headerText: {
marginTop: 50,
fontSize: 26,
color: 'white',
},
textInputWrapper: {
marginTop: 30,
alignSelf: 'stretch',
padding: 10,
},
textInput: {
padding: 10,
backgroundColor: '#EFEFEF',
borderWidth: 1,
borderColor: '#DDD',
borderRadius: 3,
},
});
At this point, your app should look like this
Now, let's create a view that would contain a preview of the image to be downloaded
// app.js
...
class App extends React.Component {
render() {
return (
<>
...
<View style={styles.app}>
...
<View style={styles.imagePreview} />
<Text>Save Image</Text>
</View>
...
</>
);
}
}
...
const styles = StyleSheet.create({
...
imagePreview: {
height: 300,
width: 300,
backgroundColor: 'purple',
marginTop: 30,
},
});
Finally, let's add a button we can use to initiate the download.
// app.js
...
import {
...
TouchableOpacity,
} from 'react-native';
class App extends React.Component {
render() {
return (
<>
...
<View style={styles.app}>
...
<View style={styles.imagePreview} />
<TouchableOpacity style={styles.downloadButton}>
<Text>Download Image</Text>
</TouchableOpacity>
</View>
...
</>
);
}
}
...
const styles = StyleSheet.create({
...
downloadButton: {
backgroundColor: 'white',
marginTop: 40,
paddingHorizontal: 40,
paddingVertical: 20,
borderRadius: 3,
},
});
At the this point, your app should look like this
Now we bind the value of the TextInput
to state
so it is accessible elsewhere.
...
class App extends React.Component {
state = {
url: '',
};
updateUrl = url => {
this.setState({url});
};
render() {
const { url } = this.state;
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.app}>
...
<View style={styles.textInputWrapper}>
<TextInput
placeholder="Enter image url here"
style={styles.textInput}
value={url}
onChangeText={text => this.updateUrl(text)}
/>
</View>
...
</SafeAreaView>
</>
);
}
}
export default App;
...
Next, we'll bind the url in state to an image componenent so it can be displayed.
import React from 'react';
import {
...
Image,
} from 'react-native';
class App extends React.Component {
...
render() {
..
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.app}>
<Text style={styles.headerText}>React Native Image Downloader</Text>
<View style={styles.textInputWrapper}>
...
</View>
{/* we've replaced the View with an Image*/}
<Image source={{uri: url}} style={styles.imagePreview} />
<TouchableOpacity style={styles.downloadButton}>
<Text>Download Image</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</>
);
}
}
...
Adding download functionality
Let's create a handleDownload
function and attach it to our TouchableOpacity
import React from 'react';
import { ... } from 'react-native';
import CameraRoll from '@react-native-community/cameraroll';
import RNFetchBlob from 'rn-fetch-blob';
class App extends React.Component {
...
handleDownload = async () => {
RNFetchBlob.config({
fileCache: true,
appendExt: 'png',
})
.fetch('GET', this.state.url)
.then(res => {
CameraRoll.saveToCameraRoll(res.data, 'photo')
.then(res => console.log(res))
.catch(err => console.log(err))
})
.catch(error => console.log(error);
};
render() {
...
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
...
<TouchableOpacity
style={styles.downloadButton}
onPress={this.handleDownload}>
<Text>Download Image</Text>
</TouchableOpacity>
...
</SafeAreaView>
</>
);
}
}
...
For android devices you need to ensure you have permission to save images.
import React from 'react';
import {
...
PermissionsAndroid,
Alert,
} from 'react-native';
import CameraRoll from '@react-native-community/cameraroll';
import RNFetchBlob from 'rn-fetch-blob';
class App extends React.Component {
...
getPermissionAndroid = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Image Download Permission',
message: 'Your permission is required to save images to your device',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
return true;
}
Alert.alert(
'Save remote Image',
'Grant Me Permission to save Image',
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
} catch (err) {
Alert.alert(
'Save remote Image',
'Failed to save Image: ' + err.message,
[{text: 'OK', onPress: () => console.log('OK Pressed')}],
{cancelable: false},
);
}
};
handleDownload = async () => {
// if device is android you have to ensure you have permission
if (Platform.OS === 'android') {
const granted = await this.getPermissionAndroid();
if (!granted) {
return;
}
}
RNFetchBlob.config({
fileCache: true,
appendExt: 'png',
})
.fetch('GET', this.state.url)
.then(res => {
...
};
render() {
...
}
}
...
Finally lets add an ActivityIndicator and some helpful feedback messages. Your final code should look like this:
Here's a little video demo.
Top comments (16)
Wow, this is exactly what I had been looking for. Nonetheless, this tutorial series is awesome. Great use of Camera Roll from React Community package along with React Native fetch blob in order to save the image from a URL to the app. The stepwise guidance on setting up and integration along with coding implementations is easy to grasp from a beginner's point of view.
Thank you Chris
Nice tut, straight to point...I have been trying to create a video gallery using RNFetchBlob to fetch videos from my device video folder to my react native app, but have not been able to achieve that...I am new to react native, please can you do tutorial on that. Thanks a lot.
Hey Benjamin, try this and see if you can modify it to your need youtube.com/watch?v=QwhrAjp4X_Y
Hi majiyd.
I have an error, when I try save image using CameraRoll.save(...) in my xiaomi device this work, but when try save in another device save the image but with error, I can't open the image and other thing, when try in a different route dont work the process to save using the library... please do you can help me to resolve this issue!
Thanks.!
I'd love to help you but i need more details
thanks a lot
Real simple and concise. Nice one, majiyd.
Thanks Chief!
What do you do if you don't know the extension of the file you're downloading?
I'm not exactly sure but I think png would work just fine regardless. Or whichever extension you choose to use.
this helped me a lot thanks
I'm glad it did
Went through several articles and SO posts until I finally found this one that got it working instantly on iOS and Android. Thanks for the write-up!
Hi, great Article... I have the next problem. I call to my API and I return inside the response return base64 encode of image.
Do you have some example for this problem?
Thanks, Alexandro. I'm sorry for the late response I did not get notified earlier.
Are you still having this problem?
You can use something like
RNFetchBlob.fetch('GET', 'example.com/images/img1.png',)
.then((res) => {
let base64Str = res.base64()
})