I'm using the following code to download a file (can be a PDF or a DOC) and then opening it using Linking.
const { dirs } = RNFetchBlob.fs;
let config = {
fileCache : true,
appendExt : extension,
addAndroidDownloads : {
useDownloadManager : false,
notification : false,
title : 'File',
description : 'A file.',
path: `${dirs.DownloadDir}/file.${extension}`,
},
};
RNFetchBlob.config(config)
.fetch(
method,
remoteUrl,
APIHelpers.getDefaultHeaders()
)
.then((res) => {
let status = res.info().status;
if (status == 200) {
Linking.canOpenURL(res.path())
.then((supported) => {
if (!supported) {
alert('Can\'t handle url: ' + res.path());
} else {
Linking.openURL(res.path())
.catch((err) => alert('An error occurred while opening the file. ' + err));
}
})
.catch((err) => alert('The file cannot be opened. ' + err));
} else {
alert('File was not found.')
}
})
.catch((errorMessage, statusCode) => {
alert('There was some error while downloading the file. ' + errorMessage);
});
However, I'm getting the following error:
An error occurred while opening the file. Error: Unable to open URL:
file:///Users/abhishekpokhriyal/Library/Developer/CoreSimulator/Devices/3E2A9C16-0222-40A6-8C1C-EC174B6EE9E8/data/Containers/Data/Application/A37B9D69-583D-4DC8-94B2-0F4AF8272310/Documents/RNFetchBlob_tmp/RNFetchBlobTmp_o259xexg7axbwq3fh6f4.pdf
I need to implement the solution for both iOS and Android.
I think the easiest way to do so is by using react-native-file-viewer package.
It allows you to Prompt the user to choose an app to open the file with (if there are multiple installed apps that support the mimetype).
import FileViewer from 'react-native-file-viewer';
const path = // absolute-path-to-my-local-file.
FileViewer.open(path, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
// error
});
So, I finally did this by replacing Linking by the package react-native-file-viewer.
In my APIHelpers.js:
async getRemoteFile(filePath, extension, method = 'GET') {
const remoteUrl = `${API_BASE_URL}/${encodeURIComponent(filePath)}`;
const { dirs } = RNFetchBlob.fs;
let config = {
fileCache : true,
appendExt : extension,
addAndroidDownloads : {
useDownloadManager : false,
notification : false,
title : 'File',
description : 'A file.',
path: `${dirs.DownloadDir}/file.${extension}`,
},
};
return new Promise(async (next, error) => {
try {
let response = await RNFetchBlob.config(config)
.fetch(
method,
remoteUrl,
this.getDefaultHeaders()
);
next(response);
} catch (err) {
error(err);
}
});
}
In my Actions.js
export function openDocument(docPath, ext) {
return async (dispatch) => {
dispatch(fetchingFile());
APIHelpers.getRemoteFile(docPath, ext).then(async function(response) {
dispatch(successFetchingFile());
let status = response.info().status;
if (status == 200) {
const path = response.path();
setTimeout(() => {
FileViewer.open(path, {
showOpenWithDialog: true,
showAppsSuggestions: true,
})
.catch(error => {
dispatch(errorOpeningFile(error));
});
}, 100);
} else {
dispatch(invalidFile());
}
}).catch(function(err) {
dispatch(errorFetchingFile(err));
});
}
}
In my Screen.js
import { openDocument } from 'path/to/Actions';
render() {
return <Button
title={'View file'}
onPress={() => this.props.dispatchOpenDocument(doc.filepath, doc.extension)}
/>;
}
.
.
.
const mapDispatchToProps = {
dispatchOpenDocument: (docPath, ext) => openDocument(docPath, ext),
}
Are you downloading it from the web? I can see the pdf path is attached at the end of the error path.
For web URLs, the protocol ("http://", "https://") must be set accordingly!
Try to append appropriate schemes to your path. Check it out from the link mentioned below.
This can be done with 'rn-fetch-blob'
RNFetchBlob.android.actionViewIntent(fileLocation, mimeType);
Related
react native pdf file download after i have not open not for android
working for ios iPhone 13 device IOS 15.5
android Pixels 3a android 11v
i think i have added everything needed in android manifest,
It works for ios devices but not for android. after i download the file i can't open it i.e. there is a download error
my code:
import { Alert, PermissionsAndroid, Platform } from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
interface IDownloadOptions {
src: string;
fileName: string;
token: string | null;
setLoading?: (loading: boolean) => void;
}
// commint
class DownloadManager {
public download = async (options: IDownloadOptions): Promise<any> => {
const { token, src, fileName, setLoading } = options;
const { dirs } = RNFetchBlob.fs;
const dirToSave = Platform.OS === 'ios' ? dirs.DocumentDir : dirs.DownloadDir;
console.log('dirToSave', dirToSave);
if (Platform.OS === 'android') {
const permission = await PermissionsAndroid.request('android.permission.WRITE_EXTERNAL_STORAGE');
if (permission !== 'granted') {
console.log('not granted');
setLoading && setLoading(false);
return;
}
}
const ios = RNFetchBlob.ios;
const android = RNFetchBlob.android;
const configfb = {
fileCache: true,
useDownloadManager: true,
notification: true,
mediaScannable: true,
title: fileName,
path: `${dirToSave}/${fileName}`,
};
console.log('config', configfb);
return new Promise((resolve, reject) => {
requestAnimationFrame(() => {
RNFetchBlob.config({
fileCache: configfb.fileCache,
// #ts-ignore
title: configfb.title,
path: configfb.path,
// #ts-ignore
appendExt: 'pdf',
addAndroidDownloads: {
...configfb,
},
})
.fetch('GET', src, {
Authorization: `Bearer ${token}`,
})
.then(res => {
const status = res.info().status;
console.log('status: ' + status);
console.log('res: ' + res);
if (status === 200) {
if (Platform.OS === 'ios') {
ios.openDocument(res.data);
}
if (Platform.OS === 'android') {
android.actionViewIntent(res.path(), 'pdf');
}
}
if (status === 500) {
Alert.alert('Server error 500');
}
setLoading && setLoading(false);
resolve(res);
})
.catch(error => {
console.log('error', error);
setLoading && setLoading(false);
reject(error);
});
});
});
};
}
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
}
};
I need to download file to user's Download directory in React Native app using rn-fetch-blob, but seems like it is not compatible with Android 10, because I am getting error:
First I ask for permissions:
try {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
await actualDownloadPDF();
} else {
ToastAndroid.show('Nepovolili jste ukládání souborů do zařízení.', ToastAndroid.LONG);
}
} catch (err) {
console.warn(err);
}
then I try to download file:
const res = await RNFetchBlob.config({
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
mime: 'application/pdf',
title: filename,
path: dirs.DownloadDir,
},
}).fetch('GET', url, {
Authorization: 'Bearer ' + Api.Bearer,
});
It seems like Android 10 requires from developer to open Intent where user select write permission to folder (
https://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory), but I am not sure how to do it, because it seems like RNFetchBlob does not have this feature, yet.
Any ideas how to make this work? It was working fine on Android 9.
const fileName = 'document.pdf';
const destPath = RNFetchBlob.fs.dirs.DownloadDir + '/' + fileName;
const config = {
fileCache : true,
path: destPath,
addAndroidDownloads: {
title: destPath,
description: `Download ${destPath}`,
useDownloadManager: false,
notification: false,
}
};
const res = await RNFetchBlob.config(config)...
I'm using this code and it works on Android 10.
I think that your if clause is wrong. My working example look like this:
import { PermissionsAndroid } from "react-native";
export const CheckFilePermissions = async (platform) => {
if(platform === 'android') {
try {
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
]);
if (granted['android.permission.READ_EXTERNAL_STORAGE'] && granted['android.permission.WRITE_EXTERNAL_STORAGE']) {
// user granted permissions
return true;
} else {
// user didn't grant permission... handle with toastr, popup, something...
return false;
}
} catch (err) {
// unexpected error
return false;
}
} else {
// platform is iOS
return true;
}
};
You can use it like:
if(await CheckFilePermissions(Platform.OS)) {
....
your code
....
Add android:requestLegacyExternalStorage="true" to in your AndroidManifest.xml
<application
...
android:requestLegacyExternalStorage="true"
>
You should give complete path to the 'path' field.
Change
path: dirs.DownloadDir
to
path: dirs.DownloadDir`/download.pdf`
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 know this link: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/#where-to-store-files
but i would like to save the file in Downloads directory. Is this possible to save the file in any path using Ionic? If so, please, share the example.
Here's the code:
downloadImage(image) {
this.platform.ready().then(() => {
const fileTransfer: TransferObject = this.transfer.create();
const imageLocation = `${cordova.file.applicationDirectory}www/assets/img/${image}`;
fileTransfer.download(imageLocation, cordova.file.externalDataDirectory + image).then((entry) => {
const alertSuccess = this.alertCtrl.create({
title: `Download Succeeded!`,
subTitle: `${image} was successfully downloaded to: ${entry.toURL()}`,
buttons: ['Ok']
});
alertSuccess.present();
}, (error) => {
const alertFailure = this.alertCtrl.create({
title: `Download Failed!`,
subTitle: `${image} was not successfully downloaded. Error code: ${error.code}`,
buttons: ['Ok']
});
alertFailure.present();
});
});
}
Basically I want save the file in location that is visible to the user.
the problem was lack of permission. Here is the working code that can download file to downloads directory:
async downloadFile() {
await this.fileTransfer.download("https://cdn.pixabay.com/photo/2017/01/06/23/21/soap-bubble-1959327_960_720.jpg", this.file.externalRootDirectory +
'/Download/' + "soap-bubble-1959327_960_720.jpg");
}
getPermission() {
this.androidPermissions.hasPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE)
.then(status => {
if (status.hasPermission) {
this.downloadFile();
}
else {
this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE)
.then(status => {
if(status.hasPermission) {
this.downloadFile();
}
});
}
});
}
To download the File to the Download directory you need to use Cordova File and FileTransfer Plugins.
import { File } from '#ionic-native/file';
import { FileTransfer } from '#ionic-native/file-transfer';
constructor(private transfer: FileTransfer) { }
fileTransfer: FileTransferObject = this.transfer.create();
//Use your File Url and name
downloadFile(file) {
// Some Loading
this.fileTransfer.download(url, this.file.externalRootDirectory +
'/Download/' + file).then(response => {
console.log(response);
this.dismissLoading();
this.presentToast('File has been downloaded to the Downloads folder. View
it..')
})
.catch(err => {
this.dismissLoading();
console.log(err)
});
}
Hope it helps.
import { File } from '#ionic-native/file';
import { FileTransfer } from '#ionic-native/file-transfer';
constructor(private file: File, private transfer: FileTransfer){}
let link = 'url_to_download_file';
let path = '';
let dir_name = 'Download'; // directory to download - you can also create new directory
let file_name = 'file.txt'; //any file name you like
const fileTransfer: FileTransferObject = this.transfer.create();
let result = this.file.createDir(this.file.externalRootDirectory, dir_name, true);
result.then((resp) => {
path = resp.toURL();
console.log(path);
fileTransfer.download(link, path + file_name).then((entry) => {
console.log('download complete: ' + entry.toURL());
}, (error) => {
console.log(error)
});
}, (err) => {
console.log('error on creating path : ' + err);
});
I know this is late, but I've always had issues with the FileTransfer plugin. Maybe it is just me. I've instead had success with the writeFile() method of the File plugin.
I'm still working on iOS, but for Android here is what I have:
import { File } from "#ionic-native/file";
constructor(private fileSystem: File) {}
Then, in whatever function you have the logic to save the file, we have:
let path = this.fileSystem.externalRootDirectory + '/Download/'; // for Android
let filename = 'myNewFile.pdf';
this.fileSystem.writeFile(path, filename, File, { replace: true }).then(() => {
this.toastCtrl.showToast('File has been downloaded. Please check your downloads folder.');
}, (err) => {
alert("Sorry. An error occurred downloading the file: " + err);
}
);
As I said, I'm still looking out for what path to use for iOS. And I'm still wondering how to pop up the notification that usually comes up when a download actually goes to the download folder. But at least I am able to save directly in the download folder of Android.
This code - ionic 3 capacitor - from josh morony takes a photo from the tmp directory and writes to the Document directory in this section using the FileSystem API the retrieves and manipulates the path
Filesystem.writeFile({
data: result.data,
path: fileName,
directory: FilesystemDirectory.Data
})
getFromPhotos() {
let options = {
resultType: CameraResultType.Uri
};
Camera.getPhoto(options).then(
(photo) => {
Filesystem.readFile({
path: photo.path
}).then((result) => {
// let date = new Date(),
// time = date.getTime(),
time = 'bilder',
fileName = time + '.jpeg';
Filesystem.writeFile({
data: result.data,
path: fileName,
directory: FilesystemDirectory.Data
}).then((result) => {
Filesystem.getUri({
directory: FilesystemDirectory.Data,
path: fileName
}).then((result) => {
console.log(result);
let path = result.uri.replace('file://', '_capacitor_');
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(path);
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
}
);
}
In ionic 3 you have to use the cordova File plugin - please google. It is pretty straight forward to understand: you define the original directory where the file is, the original name of the file, the target directory, and a new name for the file inside that function. The principle is the same.
To download the File to the Download directory you need to use Cordova File Plugin:
import { File } from '#ionic-native/file/ngx';
constructor(
private file: File,
) { }
this.file.writeFile(this.file.externalRootDirectory + '/Download/', user_log.xlsx, blob, { replace: true })
.then(() => {
alert('File has been downloaded. Please check your downloads folder.')
enter code here
},
(err) => {
alert("Sorry. An error occurred downloading the file: " + err);
enter code here
});
})
It works in Ionic 4 as well.