React Native File/Image Picker
Tags: reactnative, javascript
Published: 2024-01-04 11:34:58

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:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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
```
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
```

---

## 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" />
```

**iOS**

1. In `Podfile` add [(ref.)](https://www.npmjs.com/package/react-native-permissions):

```
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',
])
```

2. In `info.plist` add these permission:

```
	<key>NSCameraUsageDescription</key>
	<string>Camera permission required.</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>Galley permission required.</string>
```


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

```


`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,
};
```