DEV Community

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

Posted on • Updated on

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

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
Enter fullscreen mode Exit fullscreen mode

or npm

npm install @react-native-community/cameraroll rn-fetch-blob --save
Enter fullscreen mode Exit fullscreen mode

Link the native code

if your react-native version >= 0.60, autolink using

cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

else link manually with

react-native link @react-native-community/cameraroll 
react-native link rn-fetch-blob
Enter fullscreen mode Exit fullscreen mode

With this, we are done installing the required packages. Navigate to app.js

project_dir -> app.js
Enter fullscreen mode Exit fullscreen mode

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',
  },
});

Enter fullscreen mode Exit fullscreen mode

Run the app in the simulator with

react-native run-ios # or react-native run-android
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;
...
Enter fullscreen mode Exit fullscreen mode

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

...

Enter fullscreen mode Exit fullscreen mode

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

...

Enter fullscreen mode Exit fullscreen mode

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() {
    ...
  }
}

...
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
kris profile image
kris

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.

Collapse
 
majiyd profile image
majiyd

Thank you Chris

Collapse
 
wiredmatrix profile image
Benjamin Ikwuagwu

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.

Collapse
 
majiyd profile image
majiyd

Hey Benjamin, try this and see if you can modify it to your need youtube.com/watch?v=QwhrAjp4X_Y

Collapse
 
mzxryuzaki profile image
Alexandro Arauco

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.!

Collapse
 
majiyd profile image
majiyd

I'd love to help you but i need more details

Collapse
 
yujiansirius profile image
YuJianSirius

thanks a lot

Collapse
 
iamwebwiz profile image
Ezekiel Oladejo

Real simple and concise. Nice one, majiyd.

Collapse
 
majiyd profile image
majiyd

Thanks Chief!

Collapse
 
emilkeyvalue profile image
emil-keyvalue

What do you do if you don't know the extension of the file you're downloading?

Collapse
 
majiyd profile image
majiyd

I'm not exactly sure but I think png would work just fine regardless. Or whichever extension you choose to use.

Collapse
 
doutir profile image
Nathan Guedes

this helped me a lot thanks

Collapse
 
majiyd profile image
majiyd

I'm glad it did

Collapse
 
watersdr profile image
Donnie Waters

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!

Collapse
 
mzxryuzaki profile image
Alexandro Arauco

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?

Collapse
 
majiyd profile image
majiyd

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()
})