CameraRoll is a react-native native module that provides access to the local camera roll or photo library.
CameraRoll is a react-native native module that provides access to the local camera roll or photo library.
@react-native-camera-roll/camera-rollPrevious package name: @react-native-community/cameraroll
New package name: @react-native-camera-roll/camera-roll
$ npm install @react-native-camera-roll/camera-roll --save
or
$ yarn add @react-native-camera-roll/camera-roll
Linking should be automatic since react-native version 0.60. Below are instructions if auto linking does not work.
$ react-native link @react-native-camera-roll/camera-roll && npx pod-install
Libraries ➜ Add Files to [your project's name]node_modules ➜ @react-native-camera-roll/camera-roll and add RNCCameraroll.xcodeprojlibRNCCameraroll.a to your project's Build Phases ➜ Link Binary With LibrariesCmd+R)
......
Then you have to explicitly ask for the permission
```javascript
import { PermissionsAndroid, Platform } from "react-native";
import { CameraRoll } from "@react-native-camera-roll/camera-roll";
async function hasAndroidPermission() {
const getCheckPermissionPromise = () => {
if (Platform.Version >= 33) {
return Promise.all([
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_MEDIA_IMAGES),
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_MEDIA_VIDEO),
]).then(
([hasReadMediaImagesPermission, hasReadMediaVideoPermission]) =>
hasReadMediaImagesPermission && hasReadMediaVideoPermission,
);
} else {
return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE);
}
};
const hasPermission = await getCheckPermissionPromise();
if (hasPermission) {
return true;
}
const getRequestPermissionPromise = () => {
if (Platform.Version >= 33) {
return PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.READ_MEDIA_IMAGES,
PermissionsAndroid.PERMISSIONS.READ_MEDIA_VIDEO,
]).then(
(statuses) =>
statuses[PermissionsAndroid.PERMISSIONS.READ_MEDIA_IMAGES] ===
PermissionsAndroid.RESULTS.GRANTED &&
statuses[PermissionsAndroid.PERMISSIONS.READ_MEDIA_VIDEO] ===
PermissionsAndroid.RESULTS.GRANTED,
);
} else {
return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE).then((status) => status === PermissionsAndroid.RESULTS.GRANTED);
}
};
return await getRequestPermissionPromise();
}
async function savePicture() {
if (Platform.OS === "android" && !(await hasAndroidPermission())) {
return;
}
CameraRoll.save(tag, { type, album })
};save()CameraRoll.save(tag, { type, album })
…
javascript
CameraRoll.getAlbums(params);Returns a Promise with a list of albums
Parameters:
assetType : {string} : Specifies filter on asset type. Valid values are:All // defaultVideosPhotosalbumType : {string} : (iOS only) Specifies filter on type of album. Valid values are:AllAlbum // defaultSmartAlbumReturns:
Array of Album object
getPhotos()CameraRoll.getPhotos(params);
…
javascript
_handleButtonPress = () => {
CameraRoll.getPhotos({
first: 20,
assetType: 'Photos',
})
.then(r => {
this.setState({ photos: r.edges });
})
.catch((err) => {
//Error Loading Images
});
};
render() {
return (
{this.state.photos.map((p, i) => {
return (
);
})}
);
}Loading images with listeners and refetchs:
import { CameraRoll, cameraRollEventEmitter } from '@react-native-camera-roll/camera-roll';
import { useCallback, useEffect, useState } from 'react';
import { AppState, EmitterSubscription } from 'react-native';
interface GalleryOptions {
pageSize: number;
mimeTypeFilter?: Array;
}
interface GalleryLogic {
photos?: ImageDTO[];
loadNextPagePictures: () => void;
isLoading: boolean;
isLoadingNextPage: boolean;
isReloading: boolean;
hasNextPage: boolean;
}
const supportedMimeTypesByTheBackEnd = [
'image/jpeg',
'image/png',
'image/heif',
'image/heic',
'image/heif-sequence',
'image/heic-sequence',
];
export const useGallery = ({
pageSize = 30,
mimeTypeFilter = supportedMimeTypesByTheBackEnd,
}: GalleryOptions): GalleryLogic => {
const [isLoading, setIsLoading] = useState(false);
const [isReloading, setIsReloading] = useState(false);
const [isLoadingNextPage, setIsLoadingNextPage] = useState(false);
const [hasNextPage, setHasNextPage] = useState(false);
const [nextCursor, setNextCursor] = useState();
const [photos, setPhotos] = useState();
const loadNextPagePictures = useCallback(async () => {
try {
nextCursor ? setIsLoadingNextPage(true) : setIsLoading(true);
const { edges, page_info } = await CameraRoll.getPhotos({
first: pageSize,
after: nextCursor,
assetType: 'Photos',
mimeTypes: mimeTypeFilter,
...(isAndroid && { include: ['fileSize', 'filename'] }),
});
const photos = convertCameraRollPicturesToImageDtoType(edges);
setPhotos((prev) => [...(prev ?? []), ...photos]);
setNextCursor(page_info.end_cursor);
setHasNextPage(page_info.has_next_page);
} catch (error) {
console.error('useGallery getPhotos error:', error);
} finally {
setIsLoading(false);
setIsLoadingNextPage(false);
}
}, [mimeTypeFilter, nextCursor, pageSize]);
const getUnloadedPictures = useCallback(async () => {
try {
setIsReloading(true);
const { edges, page_info } = await CameraRoll.getPhotos({
first: !photos || photos.length {
if (!photos) {
loadNextPagePictures();
}
}, [loadNextPagePictures, photos]);
useEffect(() => {
const subscription = AppState.addEventListener('change', async (nextAppState) => {
if (nextAppState === 'active') {
getUnloadedPictures();
}
});
return () => {
subscription.remove();
};
}, [getUnloadedPictures]);
useEffect(() => {
let subscription: EmitterSubscription;
if (isAboveIOS14) {
subscription = cameraRollEventEmitter.addListener('onLibrarySelectionChange', (_event) => {
getUnloadedPictures();
});
}
return () => {
if (isAboveIOS14 && subscription) {
subscription.remove();
}
};
}, [getUnloadedPictures]);
return {
photos,
loadNextPagePictures,
isLoading,
isLoadingNextPage,
isReloading,
hasNextPage,
};
};deletePhotos()CameraRoll.deletePhotos([uri]);
…
javascript
CameraRoll.iosGetImageDataById(internalID, true);
…
javascript
try {
// uri 'PH://xxxx'
const fileData = await CameraRoll.iosGetImageDataById(uri);
if (!fileData?.node?.image?.filepath) return undefined;
const uploadPath = imageData.node.image.filepath; // output should be file://...
// fetch or ReactNativeBlobUtil.fetch to upload
}
catch (error) {}
Note:
Sometimes when calling iosGetImageDataById, the image/video can be downloaded from iCloud. To be able to receive the progress of this download, you need to add a listener to the onProgressUpdate event and use it to render on the UI.
The event generated will be an object containing the image id (id) and the progress of the download (progress). The id is a string with the internalID you used to call iosGetImageDataById. The progress is a double ranging from 0 to 1, where 0 represents the start of the download and 1 represents the completion of the download.
import { progressUpdateEventEmitter } from '@react-native-camera-roll/camera-roll';
useEffect(() => {
const subscription = progressUpdateEventEmitter.addListener(
'onProgressUpdate',
event => {
// Render the progress of the image / video being
// downloaded using event.id and event.progress
},
);
return () => {
subscription.remove();
};
}, []);
useCameraRoll()useCameraRoll is a utility hooks for the CameraRoll module.
import React, {useEffect} from 'react';
import {Button} from 'react-native';
import {useCameraRoll} from "@react-native-camera-roll/camera-roll";
function Example() {
const [photos, getPhotos, save] = useCameraRoll();
return <>
getPhotos()}>Get Photos
{
photos.map((photo, index) => /* render photos */)
}
;
};
…
javascript
export default function Thumbnail(props) {
const [base64Image, setBase64Image] = useState(null);
useEffect(() => {
const getThumbnail = async () => {
const options = {
allowNetworkAccess: true,
targetSize: {
height: 80,
width: 80
},
quality: 1.0
};
const thumbnailResponse = await CameraRoll.getPhotoThumbnail(props.image.uri, options);
setBase64Image(thumbnailResponse.thumbnailBase64);
};
getThumbnail();
}, []);
const extension = props.image.extension;
let prefix;
switch (extension) {
case 'png':
prefix = 'data:image/png;base64,';
break;
default:
//all others can use jpeg
prefix = 'data:image/jpeg;base64,';
break;
}
return (
);
}If you try to save media into specific album without asking for read and write permission then saving will not work, workaround is to not precice album name for IOS if you don't want to request full permission (Only ios >= 14).
No open issues yet, or sync has not completed.