DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Edited on

13

React Native File/Image Picker

The below libraries are used in React Native development for handling document picking, permissions, image cropping, file system operations, and image compression. Here's a brief explanation of each:

react-native-document-picker: This library provides a React Native wrapper for native document picker functionality, allowing users to pick documents from their device.

react-native-permissions: This library facilitates handling runtime permissions in React Native applications. It allows you to check and request permissions.

react-native-image-crop-picker: A popular library for picking and cropping images. It simplifies the process of selecting and manipulating images in a React Native app.

react-native-fs: This library provides a set of file system utilities for React Native, allowing you to perform file-related operations such as reading, writing, and deleting files.

react-native-device-info: A library to get information about the device the app is running on. getSystemVersion specifically retrieves the system version.

react-native-compressor: This library allows you to compress images in a React Native app. It provides a simple interface for compressing images efficiently.


INSTALLATION:

yarn add react-native-document-picker react-native-permissions react-native-image-crop-picker react-native-fs react-native-device-info react-native-compressor && cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

or

npm i react-native-document-picker react-native-permissions react-native-image-crop-picker react-native-fs react-native-device-info react-native-compressor && cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

Configuration:

Android

  1. Add permissions in AndroidManifest.xml
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> 
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> 
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
Enter fullscreen mode Exit fullscreen mode

iOS

  1. In Podfile add (ref.):
def node_require(script)
  # Resolve script with node to allow for hoisting
  require Pod::Executable.execute_command('node', ['-p',
    "require.resolve(
      '#{script}',
      {paths: [process.argv[1]]},
    )", __dir__]).strip
end

node_require('react-native/scripts/react_native_pods.rb')
node_require('react-native-permissions/scripts/setup.rb')

# ⬇️ uncomment wanted permissions
setup_permissions([
 # 'AppTrackingTransparency',
 # 'Bluetooth',
 # 'Calendars',
 # 'CalendarsWriteOnly',
 'Camera',
 # 'Contacts',
 # 'FaceID',
 # 'LocationAccuracy',
 # 'LocationAlways',
 # 'LocationWhenInUse',
 'MediaLibrary',
 # 'Microphone',
 # 'Motion',
 # 'Notifications',
 'PhotoLibrary',
 # 'PhotoLibraryAddOnly',
 # 'Reminders',
 # 'Siri',
 # 'SpeechRecognition',
 # 'StoreKit',
])
Enter fullscreen mode Exit fullscreen mode
  1. In info.plist add these permission:
    <key>NSCameraUsageDescription</key>
    <string>Camera permission required.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>Galley permission required.</string>
Enter fullscreen mode Exit fullscreen mode

Picker.jsx

import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

const YourComponent = () => {
  const onPressItem = async (_, index) => {
    setTimeout(async () => {
      if (index === 0) {
        const response = await requestDocumentWithPermission();
      alert(JSON.stringify(response));
      }
      if (index === 1) {
        const response = await requestGalleryWithPermission();
      alert(JSON.stringify(response));
      }
      if (index === 2) {
        const response = await requestCameraWithPermission();
      alert(JSON.stringify(response));
      }
    }, 1000);
  };

  return (
    <View>
      <TouchableOpacity onPress={() => onPressItem(_, 0)} style={styles.button}>
        <Text style={styles.buttonText}>Open Document</Text>
      </TouchableOpacity>

      <TouchableOpacity onPress={() => onPressItem(_, 1)} style={styles.button}>
        <Text style={styles.buttonText}>Open Gallery</Text>
      </TouchableOpacity>

      <TouchableOpacity onPress={() => onPressItem(_, 2)} style={styles.button}>
        <Text style={styles.buttonText}>Open Camera</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = {
  button: {
    backgroundColor: '#3498db',
    padding: 10,
    marginVertical: 10,
    borderRadius: 5,
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
  },
};

export default YourComponent;

Enter fullscreen mode Exit fullscreen mode

pickerHelper.js

import DocumentPicker, { types } from 'react-native-document-picker';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import ImageCropPicker from 'react-native-image-crop-picker';
import RNFS from 'react-native-fs';
import { getSystemVersion } from 'react-native-device-info';
import { Image } from 'react-native-compressor';

const requestDocumentWithPermission = async () => {
  try {
    if (Platform.OS === 'android') {
      // In android 13 no permission is needed

      const deviceVersion = getSystemVersion();
      let granted = PermissionsAndroid.RESULTS.DENIED;
      if (deviceVersion >= 13) {
        granted = PermissionsAndroid.RESULTS.GRANTED;
      } else {
        granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE
        );
      }
      if (granted) {
        return pickDocument();
      }
    } else {
      // Handle iOS permissions if needed
      return pickDocument();
    }
  } catch (error) {
    console.log('Error checking/requesting permissions:', error);
    return null;
  }
};

const pickDocument = async () => {
  try {
    const result = await DocumentPicker.pick({
      // allowMultiSelection: false,
      type: [DocumentPicker.types.pdf],
    });
    if (result) {
      console.log('Picked document:0', result);

      const { name, size, type, uri } = result[0];

      if (size > FILE_MAX_SIZE) {
        Alert.alert('File Size Limit Exceeded', 'Please select a file up to 2 MB.');
      } else {
        return {
          name,
          type,
          uri,
          size,
        };
      }
    }
  } catch (err) {
    if (DocumentPicker.isCancel(err)) {
      // User cancelled the document picker
      console.log('Document picker cancelled by user');
    } else {
      // Handle other errors
      console.log('Error picking document:', err);
    }
    return null;
  }
};

const requestCameraWithPermission = async () => {
  try {
    const cameraPermission = await check(PERMISSIONS.ANDROID.CAMERA);

    if (cameraPermission === RESULTS.GRANTED) {
      console.log('Camera permission already granted');
      return pickImageFromCamera();
    }
    const cameraPermissionResult = await request(PERMISSIONS.ANDROID.CAMERA);

    if (cameraPermissionResult === RESULTS.GRANTED) {
      console.log('Camera permission granted');
      return pickImageFromCamera();
    }
    console.log('Camera permission denied');
  } catch (error) {
    console.log('Error checking/requesting camera permission:', error);
    return null;
  }
};

const requestGalleryWithPermission = async () => {
  try {
    if (Platform.OS === 'android') {
      const deviceVersion = getSystemVersion();
      let granted = PermissionsAndroid.RESULTS.DENIED;
      if (deviceVersion >= 13) {
        granted = PermissionsAndroid.RESULTS.GRANTED;
      } else {
        granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE
        );
      }
      if (granted) {
        return pickImageFromGallery();
      }
    } else {
      // On iOS, permissions are typically not required for accessing the photo library
      console.log('iOS platform: No specific permissions required for media library');
      return pickImageFromGallery();
    }
  } catch (error) {
    console.log('Error checking/requesting storage permission:', error);
    return null;
  }
};

function getFileExtension(uri) {
  const lastDotIndex = uri.lastIndexOf('.');
  if (lastDotIndex !== -1) {
    return uri.slice(lastDotIndex + 1);
  }
  return null; // or an appropriate default value
}

const pickImageFromCamera = async () => {
  try {
    const image = await ImageCropPicker.openCamera({
      // width: 300,
      // height: 400,
      cropping: true,
      multiple: false,
      mediaType: 'photo',
    });

    if (image) {
      const pathCompressed = await Image.compress(image?.path, {
        // compress image below 2mb
        maxWidth: 1500,
        maxHeight: 1000,
      });
      const imageCompressed = await RNFS.stat(pathCompressed);

      console.log('Picked image from gallery:0', image);
      if (imageCompressed.size > FILE_MAX_SIZE) {
        Alert.alert('File Size Limit Exceeded', 'Please select a file up to 2 MB.');
      } else {
        // The picked document is available in the 'result' object

        return {
          name: image?.filename || `image_${Date.now()}.${getFileExtension(imageCompressed?.path)}`,
          type: image?.mime,
          uri: imageCompressed?.path,
          size: imageCompressed?.size,
        };
      }
    }
  } catch (error) {
    console.log('Error picking image from camera:', error);
    return null;
  }
};

const pickImageFromGallery = async () => {
  try {
    const image = await ImageCropPicker.openPicker({
      // width: 300,
      // height: 400,
      // cropping: true,
      multiple: false,
      mediaType: 'photo',
    });

    if (image) {
      const pathCompressed = await Image.compress(image?.path, {
        maxWidth: 1500,
        maxHeight: 1000,
      });
      const imageCompressed = await RNFS.stat(pathCompressed);

      console.log('Picked image from gallery:0', image, imageCompressed);
      if (imageCompressed.size > FILE_MAX_SIZE) {
        Alert.alert('File Size Limit Exceeded', 'Please select a file up to 2 MB.');
      } else {
        // The picked document is available in the 'result' object

        return {
          name: image?.filename || `image_${Date.now()}.${getFileExtension(imageCompressed?.path)}`,
          type: image?.mime,
          uri: imageCompressed?.path,
          size: imageCompressed?.size,
        };
      }
    }
  } catch (error) {
    console.log('Error picking image from gallery:', error);
    return null;
  }
};

const uploadFileImageOrPdf = async (fileBlob, isFromImagePicker = true) => {
  try {
    const formData = new FormData();
    if (isFromImagePicker) {
      formData.append('file', {
        uri: fileBlob?.path,
        type: fileBlob?.mime,
        name: fileBlob?.filename, // Adjust the filename as needed
      });
    } else {
      formData.append('file', {
        uri: fileBlob[0]?.uri,
        type: fileBlob[0]?.type,
        name: fileBlob[0]?.name, // Adjust the filename as needed
      });
    }

    const response = await fetch('YOUR_API_ENDPOINT', {
      method: 'POST',
      body: formData,
      headers: {
        'Content-Type': 'multipart/form-data',
        // You may need to include additional headers depending on your API requirements
      },
    });

    const responseData = await response.json();
    console.log('File upload response:', responseData);
  } catch (error) {
    console.log('File upload error:', error);
  }
};

export {
  requestDocumentWithPermission,
  requestCameraWithPermission,
  requestGalleryWithPermission,
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more