Waldo sessions now support scripting! – Learn more
App Development

Fetching and Using Blob Data in React Native: Demystified

Juan Reyes
Juan Reyes
Fetching and Using Blob Data in React Native: Demystified
May 24, 2022
20
min read

As we work our way into the different aspects of development in our journey to become proficient, we'll find that one of the most critical factors in building services is the layer between application and API.

For most developers working on mobile and web, this is a crucial aspect of their work that demands having an intimate understanding of the intricacies of fetching and handling data from APIs and the cloud.

One of the most popular methods for handling files and data fetched from the cloud is the blob, or binar large object. In this post, we'll explore how to fetch and handle blob data in React Native applications properly.

To put the concepts that we'll explain into practice, we'll be implementing the react-native-fetch-blob package library, which you can find here.

This package library covers all aspects of blob data management and the fetch API necessary for any project. Nevertheless, we'll provide you with a basic fetch alternative implemented in Expo in case your requirements are more basic.

So, let's jump right into it.

What Is a Blob?

According to Wikipedia: "A binary large object (BLOB or blob) is a collection of binary data stored as a single entity. Blobs are typically images, audio, or other multimedia objects, though sometimes binary executable code is stored as a blob."

What this means is that a blob is essentially a container for data, so it's more easily handled by protocols that fetch and manipulate large media files. So, if you're planning on working in an application with limited resources handling media files like photos or music, you'll be handling blobs.

react native fetch pull quote

What Is the Fetch API in React Native?

A fetch is a network process where a request is made to a remote server to retrieve or submit a resource.

For this purpose, the fetch API provides a generic definition for the Request and Response objects as well as other aspects of network requests, serving as a mechanism to retrieve resources and interact with a REST API or the cloud.

As described in the React Native docs: "React Native provides the Fetch API for your networking needs. Fetch will seem familiar if you have used XMLHttpRequest or other networking APIs before."

How to Fetch Data in React Native

Using the fetch API to retrieve data in a React Native application is very simple. All you have to do is create an async process that uses the fetch method already available in React Native and follow the asynchronous structure for handling network processes to avoid errors.

An example of a simple implementation would be the following.

 
 
import { FlatList, Text, View } from 'react-native';
import React, { useEffect, useState } from 'react';
export default function MyFetchedFlatList() {
    const [data, setData] = useState([]);
    const getData = async () => {
       try {
            const response = await fetch('https://reactnative.dev/movies.json');
            const json = await response.json();
            setData(json.movies);
        } catch (error) {
            console.error(error);
        }
    }
    useEffect(() => {
      getData();
    }, []);
  return (
    <View>
      <FlatList
          data={data}
          keyExtractor={({ id }) => id}
          renderItem={({ item }) => (
               <Text>{item.id}) Title: {item.title}, Released on {item.releaseYear}</Text>
          )}
        />
    </View>
  )
}

Notice that the code awaits both the fetch and the response. This behavior is because the data manipulation is asynchronous and not handled on the native side.

However, you can abstract that complexity with ES6 syntax.

 
 
import { FlatList, Text, View } from 'react-native';
import React, { useEffect, useState } from 'react';
export default function MyFetchedFlatList() {
    const [data, setData] = useState([]);
    const getData = async () => {
      fetch('https://reactnative.dev/movies.json')
        .then((response)=> response.json())
        .then((response)=> {
                  setData(response.movies);
              })
        .catch(err=> {
                  console.log(err);
              });
    }
    useEffect(() => {
      getData();
    }, []);
  return (
    <View>
      <FlatList
          data={data}
          keyExtractor={({ id }) => id}
          renderItem={({ item }) => (
               <Text>{item.id}) Title: {item.title}, Released on {item.releaseYear}</Text>
          )}
        />
    </View>
  )
}

That's easier to read.

Now, what about media files like images? Can we handle them as blobs?

Well, yes and no.

If you're using Expo, blob support is still a work in progress. But if you're developing for the React Native CLI, you absolutely can.

One example of retrieving an image with a fetch request would be the following.

 
 
import { View, Image } from 'react-native'
import React, { useEffect, useState } from 'react';
export default function MyFetchedImageBlob() {
    const [data, setData] = useState<string>('');
    const getData = async () => {
        fetch('https://picsum.photos/200')
        .then((response)=> response.blob())
        .then((response)=> {
                setData(URL.createObjectURL(response));
            })
        .catch(err=>{
                console.log(err);
            });
    }
    useEffect(() => {
      getData();
    }, []);
  return (
    <View>
      <Image source={{uri: data}} style={{width: 200, height: 200}}/>
    </View>
  )
}

Of course, there's a more efficient way to do this by providing the URL directly to the Image component. Nevertheless, this serves as a good illustration of the process.

What about uploading a file?

Well, this is also relatively simple with the fetch API.

Let's create an image picker to choose an image from our device to upload.

 
 
<View style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: 40 }}>
        <TouchableOpacity onPress={pickImage}>
          <View style={styles.ImageContainer}>
            {
                image == '' ? <Text>Select a Photo</Text> :
                <Image style={styles.ImageContainer} source={{uri:image}} />
            }
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={uploadImageToServer}
            activeOpacity={0.6}
            style={styles.button} >
            <Text style={styles.TextStyle}>
                Upload
            </Text>
        </TouchableOpacity>
  </View>

Now, let's add a method to handle the image picker event and load the image on the component.

 
 
const [image, setImage] = useState<string>('');
    var pickImage = async () => {
        let response = await ImagePicker.launchImageLibraryAsync({
            mediaTypes: ImagePicker.MediaTypeOptions.Images,
            allowsEditing: true,
            aspect: [1, 1],
            quality: 1,
        });
        if (!response.cancelled) {
            setImage(response.uri);
        }
    }

Finally, add the method to handle the upload fetch process.

 
 
var uploadImageToServer = async () => {
        const resp = await fetch(image);
        const blob = await resp.blob();
        var reader = new FileReader();
        reader.onload = () => {
            var InsertAPI = 'https://myuploadurl.com';
            var Data = { img:reader.result };
            var headers = {
                'Accept': 'application/json',
                'Content-Type': 'application.json'
            }
            fetch(InsertAPI, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify(Data)
            })
            .then((response)=> response.json())
            .then((response)=> {
                console.log(response);
            })
            .catch(err=> {
                console.log(err);
            })
        }
        reader.readAsDataURL(blob);
    }

The complete code will look like this:

 
 
import React, { useState } from 'react';
import { StyleSheet, Text, View, PixelRatio, TouchableOpacity, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function MyImageUploader() {
    const [image, setImage] = useState<string>('');
    var pickImage = async () => {
        let response = await ImagePicker.launchImageLibraryAsync({
            mediaTypes: ImagePicker.MediaTypeOptions.Images,
            allowsEditing: true,
            aspect: [1, 1],
            quality: 1,
        });
        if (!response.cancelled) {
            setImage(response.uri);
        }
    }
    var uploadImageToServer = async () => {
        const resp = await fetch(image);
        const blob = await resp.blob();
        var reader = new FileReader();
        reader.onload = () => {
            var InsertAPI = 'https://myuploadurl.com';
            var Data = { img:reader.result };
            var headers = {
                'Accept': 'application/json',
                'Content-Type': 'application.json'
            }
            fetch(InsertAPI, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify(Data)
            })
            .then((response)=> response.json())
            .then((response)=> {
                console.log(response);
            })
            .catch(err=> {
                console.log(err);
            })
        }
        reader.readAsDataURL(blob);
    }
  return (
    <View style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: 40 }}>
        <TouchableOpacity onPress={pickImage}>
          <View style={styles.ImageContainer}>
            {
                image == '' ? <Text>Select a Photo</Text> :
                <Image style={styles.ImageContainer} source={{uri:image}} />
            }
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={uploadImageToServer}
            activeOpacity={0.6}
            style={styles.button} >
            <Text style={styles.TextStyle}>
                Upload
            </Text>
        </TouchableOpacity>
    </View>
  )
}
const styles = StyleSheet.create({
    ImageContainer: {
      borderRadius: 8,
      width: 250,
      height: 250,
      borderColor: 'black',
      backgroundColor: 'lightblue',
      borderWidth: 1 / PixelRatio.get(),
      justifyContent: 'center',
      alignItems: 'center',
    },
    button: {
      width: 250,
      backgroundColor: 'lightblue',
      borderRadius: 8,
      marginTop: 20
    },
    TextStyle: {
      color: 'black',
      textAlign: 'center',
      padding: 10
    }
});
simulator on phone
simulator screen with photos
simulator screen for photo upload

How to Manipulate Blob Data in React Native

So, to go a little deeper into the manipulation of blobs to provide a more robust set of features, we'll be implementing the react-native-fetch-blob package library.

To install the package, input the following command.

 
 
$ npm install --save react-native-fetch-blob

Then, link the native packages via the following command.

 
 
$ react-native link

Optionally, use the following command to add Android permissions to AndroidManifest.xml automatically.

 
 
$ RNFB_ANDROID_PERMISSIONS=true react-native link

Now, import the component into your class.

 
 
import RNFetchBlob from 'react-native-fetch-blob'

This will give you access to the RNFetchBlob API, which you can now use to fetch resources.

An example of a fetch request would be the following.

 
 
// send http request in a new thread (using native code)
RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', {
    Authorization : 'Bearer access-token...',
    // more headers  ..
  })
  // when response status code is 200
  .then((res) => {
    // the conversion is done in native code
    let base64Str = res.base64()
    // the following conversions are done in js, it's SYNC
    let text = res.text()
    let json = res.json()
  })
  // Status code is not 200
  .catch((errorMessage, statusCode) => {
    // error handling
  })

Notice that the code is transforming the data into Base64. This is recommended for small resources, like data or small images, because it runs on native code. However, it's not recommended for large files.

Additionally, you can transform the response to text or JSON.

If the response is significant, then it's recommended that you stream the file directly into the disk and cache it for processing. Then, you can manipulate it on memory. One example of doing this is with the following code.

 
 
RNFetchBlob
  .config({
    // add this option that makes response data to be stored as a file,
    // this is much more performant.
    fileCache : true,
  })
  .fetch('GET', 'http://www.example.com/file/example.zip', {
    //some headers ..
  })
  .then((res) => {
    // the temp file path
    console.log('The file saved to ', res.path())
  })

It's essential to keep in mind that the cached file is not cleared automatically. You must flush this file manually upon completion.

To do this, you need to call the flush method upon completion, like in the following example.

 
 
// remove file using RNFetchblobResponse.flush() object method
  RNFetchblob.config({
      fileCache : true
    })
    .fetch('GET', 'http://example.com/download/file')
    .then((res) => {
      // remove cached file from storage
      res.flush()
    })

To cancel a request, all you have to do is call the cancel method on the task object created from the fetch operation. A simple example of doing this would be the following.

 
 
let task = RNFetchBlob.fetch('GET', 'http://example.com/file/1')
task.then(() => { ... })
    // handle request cancelled rejection
    .catch((err) => {
        console.log(err)
    })
// cancel the request, the callback function is optional
task.cancel((err) => { ... })

Notice that we're keeping a reference to the fetch operation on a variable called "task," which we can then cancel.

react native fetch pull quote

How to Authenticate a Fetch Request in React Native

There are several ways to provide authentication credentials to our fetch request. In this case, you'll be providing an authentication token that has been previously provided with a separate authentication pipeline.

Once you have an authentication token, you can provide it through the header properties in the fetch method.

Here's an example of a file upload request to Dropbox using an authentication token.

 
 
RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
    Authorization : "Bearer access-token...",
    'Dropbox-API-Arg': JSON.stringify({
      path : '/img-from-react-native.png',
      mode : 'add',
      autorename : true,
      mute : false
    }),
    'Content-Type' : 'application/octet-stream',
    // here's the body you're going to send, should be a BASE64 encoded string
    // (you can use "base64"(refer to the library 'mathiasbynens/base64') APIs to make one).
    // The data will be converted to "byte array"(say, blob) before request sent.
  }, base64ImageString)
  .then((res) => {
    console.log(res.text())
  })
  .catch((err) => {
    // error handling ..
  })

Now, if you're trying to submit some form data so that your user can provide data to the server, you can do so with a multipart/form request.

The following example illustrates how to do this.

 
 
RNFetchBlob.fetch('POST', 'http://www.example.com/upload-form', {
    Authorization : "Bearer access-token",
    'Content-Type' : 'multipart/form-data',
  }, [
    // element with property `filename` will be transformed into `file` in form data
    { name : 'avatar', filename : 'avatar.png', data: binaryDataInBase64},
    // custom content type
    { name : 'avatar-png', filename : 'avatar-png.png', type:'image/png', data: binaryDataInBase64},
    // part file from storage
    { name : 'avatar-foo', filename : 'avatar-foo.png', type:'image/foo', data: RNFetchBlob.wrap(path_to_a_file)},
    // elements without property `filename` will be sent as plain text
    { name : 'name', data : 'user'},
    { name : 'info', data : JSON.stringify({
      mail : 'example@example.com',
      tel : '12345678'
    })},
  ]).then((resp) => {
    // ...
  }).catch((err) => {
    // ...
  })

Finally, if you want to provide feedback to the user so they know how much of the upload or download is completed, you can do so with the uploadProgress and progress methods.

Here's an example of both an upload and download progress handler.

 
 
RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
      //... some headers,
      'Content-Type' : 'octet-stream'
    }, base64DataString)
    // listen to upload progress event
    .uploadProgress((written, total) => {
        console.log('uploaded', written / total)
    })
    // listen to download progress event
    .progress((received, total) => {
        console.log('progress', received / total)
    })
    .then((resp) => {
      // ...
    })
    .catch((err) => {
      // ...
    })

Keep in mind that you only need to implement one of them for them to work.

Moving On

The options are plentiful in terms of features and mechanisms to handle network transactions. Given that much of React Native is built on the rich and robust library of code and packages available in JavaScript, iOS, and Android, the ecosystem of features and alternatives is vast and strong.

However, there's also the possibility that some code just doesn't play nice with the native code in some scenarios.

That's why it's crucial to have a robust and flexible testing mechanism to ensure your project's quality and stability.

If you don't yet have this layer of security, we recommend that you check out Waldo's code-free testing solution here.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.