DEV Community

Cover image for Transform your React Native app with offline audio & video downloads!
Amit Kumar
Amit Kumar

Posted on • Edited on

Transform your React Native app with offline audio & video downloads!

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

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

2). Navigate to the project directory:

cd OfflineDownloadApp
Enter fullscreen mode Exit fullscreen mode

3).Install the required dependencies:

npm install --save rn-fetch-blob
--- or ---
yarn add rn-fetch-blob
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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 => {});
};

Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
dialzara profile image
Dialzara

Good post

Collapse
 
pecabum profile image
Petar Petrov

rn-fetch-blob is not maintained anymore but there is a very promising fork of it. Keep it in mind.