I want users to be able to take photos from within my app and have the photos save to their gallery (so that I can later view them in a photo-picker).
I have the following code from react-native-camera, it's basically the bare-bones demo code.
takePicture() {
const options = { quality: 0.5, fixOrientation: false, width: 1920 };
if (this.camera) {
this.camera
.takePictureAsync(options)
.then(data => {
this.saveImage(data.uri);
})
.catch(err => {
console.error("capture picture error", err);
});
} else {
console.error("No camera found!");
}
}
}
To move the attachment, I am using react-native-fs, as follows (more basic demo-y code):
const dirHome = Platform.select({
ios: `${RNFS.DocumentDirectoryPath}/Pictures`,
android: `${RNFS.ExternalStorageDirectoryPath}/Pictures`
});
const dirPictures = `${dirHome}/MyAppName`;
saveImage = async filePath => {
try {
// set new image name and filepath
const newImageName = `${moment().format("DDMMYY_HHmmSSS")}.jpg`;
const newFilepath = `${dirPictures}/${newImageName}`;
// move and save image to new filepath
const imageMoved = await this.moveAttachment(filePath, newFilepath);
console.log("image moved: ", imageMoved);
} catch (error) {
console.log(error);
}
};
moveAttachment = async (filePath, newFilepath) => {
return new Promise((resolve, reject) => {
RNFS.mkdir(dirPictures)
.then(() => {
RNFS.moveFile(filePath, newFilepath)
.then(() => {
console.log("FILE MOVED", filePath, newFilepath);
resolve(true);
})
.catch(error => {
console.log("moveFile error", error);
reject(error);
});
})
.catch(err => {
console.log("mkdir error", err);
reject(err);
});
});
};
When taking a photo, this code executes and prints that the image has been moved within a couple of seconds. But, when I look into the built in Gallery App on the device, it often takes several minutes for the image to finally load. I've tried this across many different devices, both emulated and physical... am I doing something wrong? Thanks!
This was caused by Android's Media Scanner not immediately realizing new files existed.
From this Git issue and the subsequent PR: https://github.com/itinance/react-native-fs/issues/79
I modified my code as follows:
saveImage = async filePath => {
try {
// set new image name and filepath
const newImageName = `${moment().format("DDMMYY_HHmmSSS")}.jpg`;
const newFilepath = `${dirPicutures}/${newImageName}`;
// move and save image to new filepath
const imageMoved = await this.moveAttachment(filePath, newFilepath).then(
imageMoved => {
if (imageMoved) {
return RNFS.scanFile(newFilepath);
} else {
return false;
}
}
);
console.log("image moved", imageMoved);
} catch (error) {
console.log(error);
}
};
using RNFS's scanFile method to force the Media Scanner to realize the file exists. This is rough code I'll need to clean up, but it gets the job done.
Related
I’m downloading a file, and I’ve expected open the file after finished downloading, but this does not work, it’s viewed on a black screen when opening using Linking, and I open this file in the folder where it is and it opens correctly. How can I fix this?
This is the URL of the file in STORAGE:
content://com.android.externalstorage.documents/tree/primary%3ADownload%2FSigaa/document/primary%3ADownload%2FSigaa%2Fhistory.pdf
My code:
const downloadPath = FileSystem.documentDirectory! + (Platform.OS == "android" ? "" : "");
const ensureDirAsync: any = async (dir: any, intermediates = true) => {
const props = await FileSystem.getInfoAsync(dir);
if (props.exists && props.isDirectory) {
return props;
}
let _ = await FileSystem.makeDirectoryAsync(dir, { intermediates });
return await ensureDirAsync(dir, intermediates);
};
const downloadFile = async (fileUrl) => {
if (Platform.OS == "android") {
const dir = ensureDirAsync(downloadPath);
}
let fileName = "history";
const downloadResumable = FileSystem.createDownloadResumable(
fileUrl,
downloadPath + fileName,
{}
);
console.log(downloadPath);
try {
const { uri } = await downloadResumable.downloadAsync();
saveAndroidFile(uri, fileName);
} catch (e) {
console.error("download error:", e);
}
};
const saveAndroidFile = async (fileUri, fileName = "File") => {
try {
const fileString = await FileSystem.readAsStringAsync(fileUri, { encoding: FileSystem.EncodingType.Base64 });
if (local === "") {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
return;
}
await AsyncStorage.setItem("local", permissions.directoryUri);
}
try {
await StorageAccessFramework.createFileAsync(local, fileName, "application/pdf")
.then(async (uri) => {
await FileSystem.writeAsStringAsync(uri, fileString, { encoding: FileSystem.EncodingType.Base64 });
Linking.openURL(uri);
alert("Success!");
})
.catch((e) => {});
} catch (e) {
throw new Error(e);
}
} catch (err) {}
};
The black screen when opening:
I solved this by implementing the intent launcher api for android and the sharing api for ios.
To make it work it is important to provide the mimeType of the file (or UTI for IOS). You can extract it manually from the file extension but there's a library for that on RN: react-native-mime-types.
Actually I think any library that doesn't deppend on node core apis shoud work just fine.
Here's the code:
const openFile = async (fileUri: string, type: string) => {
try {
if (Platform.OS === 'android') {
await startActivityAsync('android.intent.action.VIEW', {
data: fileUri,
flags: 1,
type,
});
} else {
await shareAsync(fileUri, {
UTI: type,
mimeType: type,
});
}
} catch (error) {
// do something with error
}
};
My requirement is to upload the filename with special characters also. My code for android device is :
if (self.device.platform == 'Android') {
let permissions = cordova.plugins.permissions;
self.UserUtils.addPermission(permissions.READ_EXTERNAL_STORAGE).then((success) => {
self.fileChooser.open().then((path: any) => {
console.log(path);
console.log("fileChooser successCallback");
(window as any).FilePath.resolveNativePath(path, function (path: any) {
let a = path.split('/');
let fileName = a.pop();
//fileName = fileName.replace(/[&\/\\#,+()$~%#£=!-'":*?<>{}]/g,'_').replace(/_{2,}/g,'_');
let fileObj = self.fileService.getFileNameExt(fileName);
let onlyName = fileObj.onlyName;
let ext = fileObj.ext;
let p = a.join().replace(/,/g, "/");
p = p + "/";
console.log(p, fileName);
self.file.checkFile(p, fileName)
.then(function (suc) {
console.log(suc,"suc");
self.file.readAsBinaryString(p, fileName)
.then(function (success: any) {
console.log("readAsBinaryString sucess");
self._zone.run(() => {
self.attachments[id] = {
docName: onlyName,
docType: ext,
scoreDocument: btoa(success),
// scoreDocument: success,
delete: true
};
});
console.log("Attachment::",self.attachments[id]);
}).catch(function (error) {
console.log("readAsBinaryString fail", error);
});
}).catch(function (err) {
console.log("checkFile false");
console.log(err);
console.log(JSON.stringify(err));
});
}, self.failureCallback);
}).catch(e => console.log(e));
}, (err) => {
console.log(err);
})
}
I have tried the below scenarios:
a. Removed file check File function. With this there is no need to replace special characters but it does not work with Motorola devices. Resolve Native Path gives error.
b. If i replace special characters then check File function resolves to false.
I'm trying to implement a simple application using Ionic v4 angular and cordova. Just select a photo and upload it to a parse server (back4app.com). But I couldn't do it.
This is my code:
home.page.ts
import { ParseService } from '../service/parse.service';
import { Camera, CameraOptions } from '#ionic-native/camera/ngx';
import { File } from '#ionic-native/file/ngx';
import { WebView } from '#ionic-native/ionic-webview/ngx';
selectPhoto() {
const options: CameraOptions = {
quality: 100,
sourceType: this.camera.PictureSourceType.PHOTOLIBRARY,
destinationType: this.camera.DestinationType.FILE_URI,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE,
correctOrientation: true
}
this.camera.getPicture(options).then((imageData) => {
// imageData is either a base64 encoded string or a file URI
// If it's base64 (DATA_URL):
// let base64Image = 'data:image/jpeg;base64,' + imageData;
// console.log(imageData);
this.fileToUpload = imageData;
this.phototoshow = this.webview.convertFileSrc(imageData);
}, (err) => {
// Handle error
});
}
onSubmit() {
this.presentLoading();
// return this.apiService.upload(this.phototoshow).subscribe((data: any) => {
return this.apiService.upload(this.fileToUpload).subscribe((data: any) => {
console.log(data);
}
}
service.ts
upload(img1): Observable<any> {
// console.log(data);
return this.http.post(this.apiURL + '/files/img.jpg', img1,{
headers: {
'X-Parse-Application-Id': this.APP_ID,
'X-Parse-REST-API-Key': this.REST_API_KEY,
'Content-Type':'image/jpeg'
}
})
.pipe(
retry(1),
catchError(this.handleError)
)
}
I was able to upload the image with "input type = file" in the form ... but selecting the image from the gallery with the cordova plugin camera ... it only returns FILE_URI but I need the OBJECT FILE to upload via api rest.
I have read enough info on the web but it is old information that does not help me. I hope someone can help me with the problem. thanks
I managed to solve the problem:
startUpload() {
this.file.resolveLocalFilesystemUrl(this.fileToUpload)
// this.file.resolveLocalFilesystemUrl(imgEntry.filePath)
.then(entry => {
(entry as FileEntry).file(file => this.readFile(file))
})
.catch(err => {
alert('Error while reading file.');
});
}
readFile(file: any) {
const reader = new FileReader();
reader.onload = () => {
const imgBlob = new Blob([reader.result], {
type: file.type
});
this.onSubmit(imgBlob);
};
reader.readAsArrayBuffer(file);
}
I want to save images using Camera in angular but the image always saves in the cache folder off my app :
http://localhost/_app_file_/storage/emulated/0/Android/data/io.ionic.starter/cache/my_image.jpeg
Here is the code to take a picture :
image: any = '';
async openCam() {
const options: CameraOptions = {
quality: 50,
destinationType: this.camera.DestinationType.NATIVE_URI,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE
};
try {
const imageData = await this.camera.getPicture(options);
return this.image = (window as any).Ionic.WebView.convertFileSrc(imageData);
} catch (err) {
// Handle error
if (JSON.stringify(err) !== '"No Image Selected"') {
alert('error ' + JSON.stringify(err));
}
}
}
Here is the code to save and get the image :
async saveItem(item, value, secondValue) {
this.nativeStorage.setItem(item, { one: value, two: secondValue })
.then(() => console.log('Stored item!'))
.catch(error => console.error('Error storing item', error));
}
async getItem(item) {
return await this.nativeStorage.getItem(item);
}
I want to be able to store the image in the file of the app not in the cache
Ty for your responses
As the title says, I'm trying to upload Image to firebase in react native. I'm using react-native-image-picker and firebase modules for that. My code goes as: (Only including the "main" parts for clarity)
import ImagePicker from 'react-native-image-picker';
...
//called on pressing a button
onChooseImagePress = async () => {
let result = await ImagePicker.open({ //error occurs here
takePhoto: true,
useLastPhoto: true,
chooseFromLibrary: true
});
if (!result.cancelled) {
this.uploadImage(result.uri, "test-image")
.then(() => {
Alert.alert("Success");
})
.catch((error) => {
Alert.alert(error);
});
}
}
uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase.storage().ref('images').child("userName/" + imageName);
return ref.put(blob);
}
....
Issue:
I am getting this error: undefined is not a function. Here's a screenshot of the same:
I'm not sure what it even means, since ImagePicker has an open function. Please note that I have provided the desired permissions. So it is not an issue due to that. Please help me resolve this. Thanks...
Are you using React-native ImagePicker? There is no open in the API document.
API Reference of react-native-image-picker
This is the default example of getting the value of the selected image you want.
import ImagePicker from 'react-native-image-picker';
// More info on all the options is below in the API Reference... just some common use cases shown here
const options = {
title: 'Select Avatar',
customButtons: [{ name: 'fb', title: 'Choose Photo from Facebook' }],
storageOptions: {
skipBackup: true,
path: 'images',
},
};
/**
* The first arg is the options object for customization (it can also be null or omitted for default options),
* The second arg is the callback which sends object: response (more info in the API Reference)
*/
ImagePicker.launchImageLibrary(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
this.setState({
avatarSource: source,
});
}
});