React Native Series: How to save an image from a remote url in React Native
Tags: reactnative, android, ios, cameraroll
Published: 2020-06-14 21:37:01

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](https://github.com/majiyd/saveRemoteImage)

I'm assuming you already have node and react-native set up, if you don't, please check out the official [docs](https://facebook.github.io/react-native/docs/getting-started) to learn how to set up a react native project.

The following packages are required for this tutorial
- [@react-native-community/cameraroll](https://www.npmjs.com/package/@react-native-community/cameraroll)
- [rn-fetch-blob](https://github.com/joltup/rn-fetch-blob#readme)

## Installing the packages 
Install the packages using yarn
```bash
yarn add @react-native-community/cameraroll rn-fetch-blob
```

or npm

```bash
npm install @react-native-community/cameraroll rn-fetch-blob --save
```
Link the native code

if your react-native version >= 0.60, autolink using 
```bash
cd ios && pod install
```
else link manually with
```bash
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
```js
// 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 
```bash
react-native run-ios # or react-native run-android
```
You should see something like this
![simple-react-native-app](https://dev-to-uploads.s3.amazonaws.com/i/epod1ovcaj7p25anph92.png)

## 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
```xml
<!-- 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
```
```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

![save-remote-image-react-native](https://dev-to-uploads.s3.amazonaws.com/i/k1qr56g7ivqa4918h8ba.png)

Let's build the UI. First, start by creating a view that would contain all our components

```js
// 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
```js
// 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
![react-native-app-with-text-input](https://dev-to-uploads.s3.amazonaws.com/i/iuyfl5ajqope1xfprcqu.png)

Now, let's create a view that would contain a preview of the image to be downloaded
```js
// 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.
```js
// 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
![download image from remote url with react native wip](https://dev-to-uploads.s3.amazonaws.com/i/srh1r6nn87x4pmpep2yf.png)


Now we bind the value of the  `TextInput` to `state` so it is accessible elsewhere.
```js
...

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.
```js
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>
      </>
    );
  }
}

...

```

![save-remote-image-react-native](https://dev-to-uploads.s3.amazonaws.com/i/k1qr56g7ivqa4918h8ba.png)


## Adding download functionality
Let's create a `handleDownload` function and attach it to our `TouchableOpacity`

```js
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.
```js
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:

{% gist https://gist.github.com/majiyd/cebfdfdc94a5d2d2a7af99bb27f887bf %}

Here's a little video demo.

{% youtube 3E5j5HpHHs8  %}