loading...
Cover image for React Native Series: How to save an image from a remote url in React Native

React Native Series: How to save an image from a remote url in React Native

majiyd profile image majiyd Updated on ・5 min read

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
simple-react-native-app

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

save-remote-image-react-native

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
react-native-app-with-text-input

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
download image from remote url with react native wip

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

...

save-remote-image-react-native

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.

Posted on by:

majiyd profile

majiyd

@majiyd

I Love building things

Discussion

pic
Editor guide
 

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.

 

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.

 
 

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?

 

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?