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