Transform your React Native app with offline audio & video downloads!
Tags: reactnative, android, ios, offline
Published: 2024-04-22 07:10:45

In this tutorial, we'll explore how to integrate essential features such as audio and video download, deleting specific content, clearing all downloads at once, and displaying a progress bar while downloading in a React Native application. This comprehensive guide will equip you with the tools and knowledge needed to enhance your app's offline capabilities, providing users with a seamless and interactive experience using the [rn-fetch-blob](https://www.npmjs.com/package/rn-fetch-blob) library.📱

_The Offline Content Management feature in React Native using RNFetchBlob allows users to download audio and video content for offline playback. This documentation provides a comprehensive guide on setting up, implementing, and managing offline content in a React Native application.📚🔧_

Let's dive in and unlock the potential of offline audio and video downloads in React Native!💡

## Features🎯🚀

1. **Audio and Video Download:** Seamlessly download audio and video content to enjoy offline.📥
2. **Check Download Status:** Easily check if content is already downloaded with intuitive icons.✔️❌
3. **Interactive Progress Bar:** Stay informed with a circular progress bar and percentage display during downloads.🔄📊
4. **Efficient Deletion:** Delete individual downloaded content with a single tap for decluttering.🗑️
5. **Bulk Deletion:** Clear out your offline library by deleting all downloaded content at once.🧹

**🎬 Let's kick things off with a bang - check out our captivating demo video showcasing the download service in action!🚀**

---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88rlrw363yy3p14jw31b.gif)
---

**Prerequisites**📚🛠️

- Knowledge of React Native development.
- Familiarity with JavaScript and React Native libraries.
- Installation of Node.js, npm/yarn, and React Native CLI.
- Basic understanding of file handling and storage concepts.

## Setting Up the Project🛠️🔧

**1). Create a new React Native project using the following command:**

```
npx react-native init OfflineDownloadApp
```

**2). Navigate to the project directory:**

```
cd OfflineDownloadApp
```



**3).Install the required dependencies:**

```
npm install --save rn-fetch-blob
--- or ---
yarn add rn-fetch-blob
```

**Package.json Dependencies**
Below are the dependencies specified in the `package.json` file for a React Native project:

```
{
    "dependencies": {
    "react": "18.2.0",
    "react-native": "0.72.3",
    "rn-fetch-blob": "^0.12.0"
  },
}
```


**Event Emission📢📢**

The React Native Download Service emits events using `DeviceEventEmitter` to provide feedback on download progress and completion.

- **`downloadProgress:`** Emits progress updates during the download process.
- **`downloadDone:`** Emits an event when a download is completed.
- **`downloadError:`** Emits an event in case of download errors.


**Additional Notes📝📝**

- This service uses `RNFetchBlob` for file operations and download management.
- Metadata related to downloaded content is stored locally in a JSON file.
- Error handling and notifications are managed using `react-native-toast-message`.


## Offline Content Management with RNFetchBlob in React Native 📱

## 1). Save Downloaded Content To User Device💾💾

**Overview**

The `sendDownloadedDataToLocalDir` function is a crucial part of implementing offline content management in a React Native application using RNFetchBlob. This function handles the download process for audio and video content, saves downloaded content to local storage, and emits events to track download progress.

**Parameters:**

- **`callback`:** A callback function to be executed after the download completes.
- **`contentId`:** The ID of the downloaded content.
- **`src`:** The URL of the content to be downloaded.
- **`artistName`:** The artist name associated with the content.
- **`songName`:** The name of the song or content.
- **`posterImage`:** The URL of the poster image associated with the content.
- **`isAudio`:** A boolean indicating whether the content is audio or video.

_These parameters can be customized according to your needs._

**Usage Example:**

`sendDownloadedDataToLocalDir(callback, contentId, src, artistName, songName, posterImage, isAudio);
`

**Code:**

```
export const sendDownloadedDataToLocalDir = async (
  callback = () => {},

  contentId,
  src,
  artistName,
  songName,
  posterImage,
  isAudio,
) => {
  const {dirs} = RNFetchBlob.fs;
  const dirToSave = Platform.OS === 'ios' ? dirs.DocumentDir : dirs.CacheDir;
  const path = RNFetchBlob.fs.dirs.CacheDir + `/.file.json`;

  var offlineMusicPlayerUrl = '';
  var imageUrl = '';
  var roundOffValue = 0;
  let getNewTime = new Date().getTime();

  const commonConfig = {
    fileCache: true,
    useDownloadManager: true,
    notification: true,
    title: songName,
    path: isAudio
      ? `${dirToSave}/${getNewTime}.mp3`
      : `${dirToSave}/${getNewTime}.mp4`,
    mediaScannable: true,
    description: 'file download',
  };

  const configOptions = Platform.select({
    ios: {
      fileCache: commonConfig.fileCache,
      title: commonConfig.title,
      path: commonConfig.path,
      appendExt: isAudio ? 'mp3' : 'mp4',
    },
    android: commonConfig,
  });

  const startDownloadingTheRestContent = async cb => {
    // for Images
    try {
      let res = await RNFetchBlob.config({
        fileCache: true,
        path: `${dirToSave}/${contentId}.webp`,
        IOSBackgroundTask: true,
      }).fetch('GET', posterImage, {});
      if (res) {
        imageUrl = res.path();
      }
    } catch (e) {}

    var offlineObjData = {
      contentId: contentId,
      source: offlineMusicPlayerUrl,
      artistName: artistName,
      songName: songName,
      downloadDate: new Date(),
      posterImage: imageUrl,
      isAudio: isAudio,
    };

    let offlinDonwloadList = [];
    //fetching local downloads from storage
    try {
      let localDownloads = await RNFetchBlob.fs.readFile(path, 'utf8');
      localDownloads = JSON.parse(localDownloads);
      if (Array.isArray(localDownloads)) {
        offlinDonwloadList = localDownloads;
      }
    } catch (e) {}

    //adding new downloads
    offlinDonwloadList.push(offlineObjData);
    await RNFetchBlob.fs
      .writeFile(path, JSON.stringify(offlinDonwloadList), 'utf8')
      .then(r => {
        cb && cb();
      })
      .catch(e => {});
  };

  // for video
  if (src) {
    RNFetchBlob.config(configOptions)
      .fetch('get', src, {})
      .progress((received, total) => {
        const percentageValue = (received / total) * 100;
        roundOffValue = Math.round(percentageValue);

        var params = {
          contentId: contentId,
          source: src,
          artistName: artistName,
          songName: songName,
          progressValue: JSON.stringify(roundOffValue),
        };
        DeviceEventEmitter.emit('downloadProgress', params);
        DeviceEventEmitter.emit('downloadProgress', params);
      })
      .then(async res => {
        let downloadContents = {};
        if (Platform.OS === 'ios') {
          await RNFetchBlob.fs.writeFile(commonConfig.path, res.data, 'base64');
          offlineMusicPlayerUrl = commonConfig.path;
          await startDownloadingTheRestContent(() => {
            var params = {
              contentId: contentId,
              source: src,
              artistName: artistName,
              songName: songName,
              progressValue: JSON.stringify(roundOffValue),
            };

            DeviceEventEmitter.emit('downloadDone', params);
            DeviceEventEmitter.emit('downloadProgress', params);
          });
        } else {
          // for Android
          offlineMusicPlayerUrl = res.path();
          startDownloadingTheRestContent(() => {
            var params = {
              contentId: contentId,
              source: src,
              artistName: artistName,
              songName: songName,
              progressValue: JSON.stringify(roundOffValue),
            };
            DeviceEventEmitter.emit('downloadDone', params);
            DeviceEventEmitter.emit('downloadProgress', params);
          });
        }
      })

      .catch(err => {
        callback('error');
        DeviceEventEmitter.emit('downloadError', true);
      });
  }
};
```

## 2). Fetch Downloaded Content To User Device🔄

**Overview**

The `fetchDownloadedDataFromLocalDir` function is responsible for retrieving downloaded data from local storage in a React Native application. It reads a JSON file containing metadata of downloaded content, parses the data, and sends it to a callback function for further processing.

**Parameters:**

- **`sendData:`** A callback function to handle the fetched downloaded data.

**Usage Example:**

`fetchDownloadedDataFromLocalDir(sendData);`

**Code:**

```
export const fetchDownloadedDataFromLocalDir = async (sendData = () => {}) => {
  const trackFolder =
    Platform.OS === 'ios'
      ? RNFetchBlob.fs.dirs.DocumentDir
      : RNFetchBlob.fs.dirs.CacheDir;
  const MyPath = RNFetchBlob.fs.dirs.CacheDir + `/.file.json`;
  await RNFetchBlob.fs
    .ls(trackFolder)
    .then(files => {})
    .catch(err => {});
  try {
    let localDownloads = await RNFetchBlob.fs.readFile(MyPath, 'utf8');
    localDownloads = JSON.parse(localDownloads);
    if (Array.isArray(localDownloads)) {
      sendData(localDownloads);
    }
  } catch (e) {}
};
```

## 3). Delete Downloaded Content To User Device🗑️

**Overview**

The `deleteContentFromLocalDir` function is responsible for deleting specific downloaded content from local storage in a React Native application. It reads a JSON file containing metadata of downloaded content, finds and removes the specified content based on its ID, and then updates the JSON file with the modified data.

**Parameters:**
**`downloadedId:`** The ID of the downloaded item to be deleted.

**Usage Example:**

`deleteContentFromLocalDir(downloadedId);
`

**Code:**

```
export const deleteContentFromLocalDir = async downloadedId => {
  let jsonObj = [];
  const MyPath = RNFetchBlob.fs.dirs.CacheDir + `/.file.json`;
  try {
    let localDownloads = await RNFetchBlob.fs.readFile(MyPath, 'utf8');
    localDownloads = JSON.parse(localDownloads);
    if (Array.isArray(localDownloads)) {
      jsonObj = localDownloads;
    }
  } catch (e) {}

  let flag = '';
  const contentIdToFind = downloadedId;
  jsonObj.map((item, index) => {
    if (item.id === contentIdToFind) {
      flag = index;
    }
  });
  jsonObj.splice(flag, 1);
  await RNFetchBlob.fs
    .writeFile(MyPath, JSON.stringify(jsonObj), 'utf8')
    .then(r => {})
    .catch(e => {});
};
```

## 4). Delete All Downloaded Content To User Device🧹🧹

**Overview**

The `deleteAllDownloadDataFromLocal` function is responsible for clearing all downloaded data from the local storage in a React Native application. It initializes an empty JSON object, converts it to a string, and writes it back to the JSON file, effectively removing all downloaded content records.

**Usage Example:**
`deleteAllDownloadDataFromLocal();`


**Code:**

```
export const deleteAllDownloadDataFromLocal = async () => {
  let jsonObj = [];
  const MyPath = RNFetchBlob.fs.dirs.CacheDir + `/.file.json`;
  await RNFetchBlob.fs
    .writeFile(MyPath, JSON.stringify(jsonObj), 'utf8')
    .then(r => {})
    .catch(e => {});
};

```

## Note:📝📝

- **Only for iOS:** When downloading audio or video content to the user's device, iOS changes the directory each time for security reasons. To handle this, the downloaded path is dynamically appended when fetching the downloaded content from the user's device, as iOS generates a new path each time.

- **For displaying downloaded images**, add "file://" before the path in Android, as images may not display correctly in Android without this prefix.

## Conclusion🎉🎉

In this documentation, we've explored the essential functions for managing downloaded data from local directories in React Native applications. These functions enable crucial operations like fetching, deleting specific content, and clearing all downloaded data from local storage. They address platform-specific considerations, such as dynamic path handling in iOS and path formatting in Android, ensuring seamless management of offline content. By leveraging these functions, developers can enhance user experience and efficiently handle downloaded data within their React Native apps. Experimenting with these functions will help tailor them to specific application requirements. Happy coding!📂📱

## After reading the post consider the following:🤔📝

- Subscribe to receive newsletters with the latest blog posts
- Download the source code for this post from my [github](https://github.com/amitmehtacode/offline)