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.
Related
my code in angular is:
async downloadFilm() {
if (confirm(this.filmData.filmTitel + ' herunterladen?')) {
if(this.api.getIsWeb()){
this.api.downloadFilm(parseInt(this.filmData.id)).subscribe(async data => {
console.log(data)
let fileName = this.filmData.filmTitel + '.mp4';
saveAs(data, fileName);
})
} else {
this.api.downloadFilm(parseInt(this.filmData.id)).subscribe(async data => {
//TODO Android Download
});
}
} else {
alert('Download abgebrochen');
}
}
i tryed using this in the place of //TODO Android Download
const fileName = this.filmData.filmTitel + '.mp4';
const fileContent = data;
await Filesystem.writeFile({
path: fileName,
data: fileContent,
directory: Directory.Documents,
encoding: Encoding.UTF8
}).then(() => {
alert('File saved');
}).catch(err => {
alert(err);
});
but when i use alert to show the error on the phone, it said "Error: "Filesystem" plugin is not implemented on android".
is there any way i can download a mp4 file to an android 12 phone using both angular and capacitor?
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`
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);
I'm working on an ionic3 application. I need to take an image from the user either by camera or gallery, first saves it to the local directory then upload the image to the server. I used the following step by step tutorial: https://devdactic.com/ionic-2-images/
Uploading the photo is working like a charm, but while saving the image to the local directory and save the path on local storage, after retrieving from storage it shows the following error: .
As it's obvious it complains about Not allowed to load local resource.
Next, I started to google for the solution, and I found this solution in StackOverflow and this in GitHub. As they both suggested, the problem is with cordova-plugin-ionic-webview, so I need to downgrade the version. When I tried their solution, the uploading and showing the image to the user is working perfectly, however, it creates problem other parts of the application which is loading data from asset no matter what; images, fonts. Shows the following error .Next I found a solutionf for the problem in GitHub here, as it suggested and accepted by most users we need to use the latest version of **cordova-plugin-ionic-webview **, which of course it would cause the first problem for me.
I'm gonna upload the codes here as well.`
getImage() {
this.presentActionSheet();
} //end getImage
public uploadImage() {
console.log('Uploading the image');
console.log(this.lastImageL);
var targetPath = this.pathForImage(this.lastImage);
console.log(targetPath);
var url = "https://dev.raihan.pomdev.net/wp-json/raihan/v1/profilePhoto";
var filename = this.lastImage;
console.log(' targetPath : ', targetPath);
console.log('File Name : ', filename)
console.log(url, " IS the URL");
var options = {
fileKey: "image",
fileName: filename,
chunkedMode: false,
mimeType: "multipart/form-data",
params: {
'image': filename,
'user_id': 79
}
};
const fileTransfer: TransferObject = this.transfer.create();
this.loading = this.loadingCtrl.create({
content: 'منتظر باشید',
});
this.loading.present();
// Use the FileTransfer to upload the image
fileTransfer.upload(targetPath, url, options).then(data => {
this.loading.dismissAll()
this.presentToast(' . عکس شما موفقانه ذخیره شد');
this.storage.set("Profile_Photo", targetPath).then((data) => {
console.log('response of uploading the image ', data);
console.log('Target Path ', targetPath);
console.log('In set storage ', targetPath);
$("#Photo").attr("src", targetPath);
$("#Photo2").attr("src", targetPath);
console.log('myphoto ', targetPath);
});
}, err => {
this.loading.dismissAll()
this.presentToast('مشکلی در قسمت ذخیره کردن عکس شما وجود دارد ' + err);
console.log('error sending the image');
console.log(err);
});
}
public takePicture(sourceType) {
var options = {
quality: 100,
sourceType: sourceType,
saveToPhotoAlbum: false,
correctOrientation: true
};
// Get the data of an image
this.camera.getPicture(options).then((imagePath) => {
if (this.platform.is('android') && sourceType === this.camera.PictureSourceType.PHOTOLIBRARY) {
this.filePath.resolveNativePath(imagePath)
.then(filePath => {
let correctPath = filePath.substr(0, filePath.lastIndexOf('/') + 1);
let currentName = imagePath.substring(imagePath.lastIndexOf('/') + 1, imagePath.lastIndexOf('?'));
this.copyFileToLocalDir(correctPath, currentName, this.createFileName());
});
} else {
var currentName = imagePath.substr(imagePath.lastIndexOf('/') + 1);
var correctPath = imagePath.substr(0, imagePath.lastIndexOf('/') + 1);
this.copyFileToLocalDir(correctPath, currentName, this.createFileName());
}
}, (err) => {
this.presentToast('Error while selecting image.');
});
}
ionViewDidLoad() {
console.log('ionViewDidLoad CaptureImagePage');
}
private createFileName() {
var d = new Date(),
n = d.getTime(),
newFileName = n + ".jpg";
return newFileName;
}
// Copy the image to a local folder
private copyFileToLocalDir(namePath, currentName, newFileName) {
this.file.copyFile(namePath, currentName, cordova.file.dataDirectory, newFileName).then(success => {
this.lastImage = newFileName;
this.uploadImage();
}, error => {
this.presentToast('Error while storing file. ' + error);
});
}
private presentToast(text) {
let toast = this.toastCtrl.create({
message: text,
duration: 5000,
position: 'center'
});
toast.present();
}
// Always get the accurate path to your apps folder
public pathForImage(img) {
if (img === null) {
return '';
} else {
return cordova.file.dataDirectory + img;
}
}
public presentActionSheet() {
let actionSheet = this.actionSheetCtrl.create({
title: 'Select Image Source',
buttons: [
{
text: 'Load from Library',
handler: () => {
this.takePicture(this.camera.PictureSourceType.PHOTOLIBRARY);
}
},
{
text: 'Use Camera',
handler: () => {
this.takePicture(this.camera.PictureSourceType.CAMERA);
}
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
actionSheet.present();
}
`
Now I'm confused which version of **cordova-plugin-ionic-webview ** I should use? Is there someone who could help me?
Note: Thanks for your patience to read all the questions.
I would try to use the latest version of the WebView if possible, and then use the window.Ionic.WebView.convertFileSrc() method on the file:/// path before putting it on a page for display. Those tips can be seen here:
https://ionicframework.com/docs/building/webview
Cordova and Capacitor apps are hosted on a local HTTP server and are
served with the http:// protocol. Some plugins, however, attempt to
access device files via the file:// protocol. To avoid difficulties
between http:// and file://, paths to device files must be rewritten
to use the local HTTP server. For example, file:///path/to/device/file
must be rewritten as http://://path/to/device/file
before being rendered in the app.
For Cordova apps, the Ionic Web View plugin provides a utility
function for converting File URIs:
window.Ionic.WebView.convertFileSrc(). There is also a corresponding
Ionic Native plugin: #ionic-native/ionic-webview.
Here is a sample method I use, which works fine in the 4.x webview:
getNormalizedUrl(path: string): SafeResourceUrl {
let newPath = this.domSanitizer.bypassSecurityTrustUrl(
window.Ionic.WebView.convertFileSrc(path));
return newPath;
}
I am using HTML input for choosing file in my Ionic3/Angular application. I am using below code:
// in .html file
<input #fileUpload type="file" name="myfile"(change)="onFileChoose($event)"/>
// in .ts file
onFileChoose($event): void {
this.fileChooser.getFileInfo($event).then((result) => {
this.fileName = result.fileName;
this.fileData = this.sanitizeFileData(result.fileData);
this.fileSize = result.fileSize;
this.fileType = result.fileType;
}, (error) => {
this.helperProvider.createAlert('Alert', 'File is corrupted.');
});
}
getFileInfo(event: Event): Promise<any> {
let target = event && event.target;
let files: Array<File> = target && target['files'];
console.log(files[0].type);
console.log(files[0].name);
return new Promise((resolve, reject) => {
if (files && files.length) {
files = Array.from(files);
let fileName = files[0].name;
let fileSize = files[0].size;
let fileType = files[0].type;
let fileReader = new FileReader();
fileReader.onload = () => resolve({
fileData: fileReader.result,
fileName: fileName,
fileSize: fileSize,
fileType: fileType
});
fileReader.onerror = error => reject(error);
fileReader.onabort = error => reject(error);
fileReader.readAsDataURL(files[0])
}
});
}
This is working fine in iOS and Browser. Both, in android and browser, i could get the original name, size and type of the file. But the problem occurs in Android.
Scenario-1(Android): When i choose an image file using the file chooser, i could get the original file name, size and type of the file.
Scenario-2(Android): When i choose a file other than image file like .pdf,.doc etc, i could not get the original file name and the type of the file. Suppose, i have choosen a file name "sample.pdf", but after i chose the file, i get the file name as a random number like 45675 and most importantly the file type, i got is empty.
Then, i researched in stackoverflow and saw these links (link1 and link2). It may be a security issue for android.
There is an ionic-native/file-chooser library but it is only for android platform.
Is there any way to force android to give the original file name?
Android does not give the original file name and file type using the above approach of mine and it is a security issue from android. So, i had to make below solution for retrieving the correct file name, file type, file size and the file data in base64.
You will need below four plugins:
FileChooser
File
FilePath
Base64
FileChooserAndroidProvider:
import {Injectable} from '#angular/core';
import {File, FileEntry, IFile} from "#ionic-native/file";
import {Base64} from "#ionic-native/base64";
import {FilePath} from "#ionic-native/file-path";
import {FileChooser} from "#ionic-native/file-chooser";
#Injectable()
export class FileChooserAndroidProvider {
constructor(private base64: Base64, private filePath: FilePath, private file: File, private fileChooser: FileChooser) {
}
getFileInfo(): Promise<any> {
return this.fileChooser.open().then((fileURI) => {
return this.filePath.resolveNativePath(fileURI).then((filePath) => {
return this.file.resolveLocalFilesystemUrl(filePath).then((fileEntry: FileEntry) => {
return new Promise((resolve, reject) => {
fileEntry.file(meta => resolve(meta), error => reject(error));
});
}).then((fileMeta: IFile) => {
return new Promise((resolve, reject) => {
return this.base64.encodeFile(filePath).then((base64Data) => {
resolve({
fileData: base64Data,
fileName: fileMeta.name,
fileSize: fileMeta.size,
fileType: fileMeta.type
})
}).catch((error) => {
reject(error);
})
})
});
});
});
}
}
FileChooserAndroidProviderModule:
import {NgModule} from '#angular/core';
import {Base64} from "#ionic-native/base64";
import {FileChooser} from "#ionic-native/file-chooser";
import {FilePath} from "#ionic-native/file-path";
import {File} from "#ionic-native/file";
#NgModule({
declarations: [],
exports: [],
providers: [
FileChooser,
File,
FilePath,
Base64
]
})
export class FileChooserAndroidProviderModule {
}
SamplePage:
constructor(private fileChooserAndroid: FileChooserAndroidProvider){}
uploadFileForAndroid(): void {
this.fileChooserAndroid.getFileInfo().then((result) => {
this.fileName = result.fileName;
this.fileData = this.sanitizeFileData(result.fileData);
this.fileSize = result.fileSize;
this.fileType = result.fileType;
}).catch((error) => {
this.helperProvider.createAlert('Alert', 'File can not be uploaded.');
});
}
SamplePageModule:
#NgModule({
declarations: [
SamplePage
],
imports: [
FileChooserAndroidProviderModule
],
providers: [
FileChooserAndroidProvider
]
})
export class SamplePageModule {
}