PEAKIQ - Software Solutions & Digital Innovation Peakiq Software Development

Peakiq Blog

Managing Background Downloads, Unzipping Files, and Notifications in React Native

A comprehensive guide to handling background downloads, ZIP extraction, and user notifications in React Native using react-native-background-downloader, react-native-zip-archive, and react-native-notifications.

Editorial4 min read756 words
Managing Background Downloads, Unzipping Files, and Notifications in React Native

Setting Up Your React Native Project

First, ensure you have a React Native project set up. If not, you can create one using:

npx react-native init MyApp

1. Install Required Libraries

We’ll be using the following libraries:

  • react-native-background-downloader for background downloads
  • react-native-zip-archive for unzipping files
  • react-native-notifications for local notifications
  • react-native-fs for file system operations

Install these libraries using npm or yarn:

npm install @kesha-antonov/react-native-background-downloader react-native-zip-archive react-native-notifications react-native-fs

or

yarn add @kesha-antonov/react-native-background-downloader react-native-zip-archive react-native-notifications react-native-fs

2. Configure Libraries

Make sure to follow the configuration instructions for each library, especially for setting up permissions on iOS and Android.

3. Implementing Background Downloads

We’ll start by setting up background downloads using react-native-background-downloader. Here’s a complete example of a React Native component that handles downloading, unzipping, and notifying the user:

import React, { useState, useEffect } from 'react';
import { Layout, Text, normalize } from 'component';
import { CustomThemeProps } from 'config/theme';
import { useThemeToggle } from 'hooks';
import styled from 'styled-components/native';
import I18n from 'i18';
import { View } from 'react-native';
import * as Progress from 'react-native-progress';
import { Notifications } from 'react-native-notifications';
import RNBackgroundDownloader from '@kesha-antonov/react-native-background-downloader';
import { unzip } from 'react-native-zip-archive';
import RNFS from 'react-native-fs';

const ToggleButton = styled.TouchableOpacity`
  background-color: ${({ theme }: CustomThemeProps) => theme.BUTTON_BG_COLOR};
`;

const ToggleButtonText = styled(Text)<{ FontSize: number }>`
  color: ${({ theme }: CustomThemeProps) => theme.BUTTON_TEXT_COLOR};
  font-size: ${(prop: { FontSize: number }) => `${normalize(prop.FontSize)}px`};
`;

const TextDesc = styled(Text)<{ color?: string }>`
  margin: ${normalize(2)}px;
  color: ${({ theme, color }: CustomThemeProps & { color?: string }) =>
    color ? color : theme.BUTTON_TEXT_COLOR};
  padding: 12px;
  font-weight: bold;
  background-color: ${({ theme }: CustomThemeProps) => theme.BUTTON_BG_COLOR};
  margin: 10px;
`;

const Login: React.FC = () => {
  const { toggleTheme, ThemeName } = useThemeToggle();
  const [ProgressLoad, SetProgressLoad] = useState<number>(0);

  useEffect(() => {
    // Request notification permissions for iOS
    Notifications.registerRemoteNotifications();
    checkExistingDownloads();
  }, []);

  const checkExistingDownloads = async () => {
    const existingTasks = await RNBackgroundDownloader.checkForExistingDownloads();
    const existingTask = existingTasks.find(task => task.id === 'file_11111');

    if (existingTask) {
      console.log(`Task ${existingTask.id} is already in progress or completed.`);
      existingTask
        .progress(({ bytesDownloaded, bytesTotal }) => {
          const progress: number = bytesDownloaded / bytesTotal;
          SetProgressLoad(progress);
        })
        .done(() => {
          console.log('Existing download is done!');
        })
        .error(error => {
          console.log('Existing download error: ', error);
        });
    } else {
      console.log('No existing task found, ready to start new download.');
    }
  };

  const sendLocalNotification = () => {
    const fireDate = new Date(Date.now() + 1 * 1000); // 1 second from now
    Notifications.postLocalNotification({
      body: 'File Uploading!',
      title: 'Your File Uploaded successfully done...',
      sound: 'chime.aiff',
      silent: false,
      category: 'SOME_CATEGORY',
      userInfo: {},
      fireDate: fireDate.toISOString(), // Converting to ISO string format
    });
  };

  const collectFilePaths = async (dirPath: string, filePaths: string[] = []): Promise<string[]> => {
    try {
      const contents = await RNFS.readDir(dirPath);
      for (const item of contents) {
        if (item.isDirectory()) {
          await collectFilePaths(item.path, filePaths);
        } else if (item.isFile()) {
          filePaths.push(item.path);
        }
      }
      return filePaths;
    } catch (error: any) {
      console.error('Reading directory failed:', error.message);
      throw error;
    }
  };

  const Download_and_Unzip = async () => {
    try {
      const Url = 'http://localhost:5005/download';
      const FilePath = await downloadFile(Url);
      const UnzipFolder = await unzip(
        FilePath,
        `${RNBackgroundDownloader.directories.documents}/file_${Date.now()}`,
      );
      const allFilePaths = await collectFilePaths(UnzipFolder);
      console.log({ allFilePaths });
      sendLocalNotification();
    } catch (error) {
      console.log(error);
    }
  };

  const downloadFile = (url: string) => {
    return new Promise<string>((resolve, reject) => {
      const destination = `${
        RNBackgroundDownloader.directories.documents
      }/file_${11111}.zip`;
      const task = RNBackgroundDownloader.download({
        id: `file_${11111}`,
        url: url,
        destination,
      })
        .progress(({ bytesDownloaded, bytesTotal }) => {
          const progress: number = bytesDownloaded / bytesTotal;
          SetProgressLoad(progress);
        })
        .done(() => {
          resolve(destination);
        })
        .error(error => {
          reject(error);
        });
    });
  };

  return (
    <Layout style={{ flex: 1 }}>
      <ToggleButton onPress={toggleTheme}>
        <ToggleButtonText FontSize={18}>
          {I18n.t('THEMEIS')} : {ThemeName}
        </ToggleButtonText>
      </ToggleButton>
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        {ProgressLoad !== 0 && (
          <>
            <Text>{`Overall Progress: ${(ProgressLoad * 100).toFixed(2)}%`}</Text>
            <Progress.Bar progress={ProgressLoad} width={350} />
          </>
        )}
        <TextDesc onPress={Download_and_Unzip} FontSize={18}>
          Download
        </TextDesc>
      </View>
    </Layout>
  );
};

export default Login;

Explanation

  1. Background Downloads: We use react-native-background-downloader to manage file downloads in the background. The downloadFile function initiates the download, while checkExistingDownloads verifies if a download task is already in progress.
  2. Unzipping Files: After downloading, the unzip function extracts the contents of the zip file. The collectFilePaths function recursively retrieves file paths from the extracted directory.
  3. Notifications: We use react-native-notifications to notify users about the progress or completion of the download.

Conclusion

In this guide, we’ve covered how to manage background downloads, unzip files, and send notifications in a React Native app. By integrating these features, you can provide a more robust and engaging user experience. Feel free to customize and extend this implementation based on your app’s requirements.