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
}
};
Related
The following code works correctly for image files. But when I'm trying to save PDF file or other not-media formates, I get Could not create asset error.
I understand that expo-media-library is designed to work with media format files.
Is there any alternative for expo-media-library to save other files formats?
import * as FileSystem from 'expo-file-system'
import * as Permissions from 'expo-permissions'
import * as MediaLibrary from 'expo-media-library'
const downloadFile = async (uri: string) => {
const targetUri = FileSystem.documentDirectory + getFileName(uri)
const downloadedFile = await FileSystem.downloadAsync(uri, targetUri)
if (downloadedFile.status === 200) {
if (Platform.OS === 'android') {
const permission = await Permissions.askAsync(Permissions.MEDIA_LIBRARY)
if (permission.status !== 'granted') {
return
}
const asset = await MediaLibrary.createAssetAsync(downloadedFile.uri)
const album = await MediaLibrary.getAlbumAsync('Download')
await MediaLibrary.addAssetsToAlbumAsync([asset], album, false)
}
}
}
it works on android device with https://docs.expo.dev/versions/latest/sdk/filesystem/#storageaccessframeworkcreatefileasyncparenturi-string-filename-string-mimetype-string
import * as FileSystem from 'expo-file-system';
import { StorageAccessFramework } from 'expo-file-system';
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
return;
}
try {
await StorageAccessFramework.createFileAsync(permissions.directoryUri, fileName, 'application/pdf')
.then((r) => {
console.log(r);
})
.catch((e) => {
console.log(e);
});
} catch((e) => {
console.log(e);
});
My pdf is well downloaded !
In my case i had to generate the file from a base64 string.
My code :
import * as FileSystem from 'expo-file-system';
import { StorageAccessFramework } from 'expo-file-system';
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
return;
}
const base64Data = 'my base 64 data';
try {
await StorageAccessFramework.createFileAsync(permissions.directoryUri, fileName, 'application/pdf')
.then(async(uri) => {
await FileSystem.writeAsStringAsync(uri, base64Data, { encoding: FileSystem.EncodingType.Base64 });
})
.catch((e) => {
console.log(e);
});
} catch (e) {
throw new Error(e);
}
i'm trying uploading multipart file to backend server using axios in react-native. I'm setting image using array, but i don't know how to transform object Object to object File. back-end server needs File type. How can i do??
expect log: [object File],
result log: [object Object]
My front code is here
//...//
interface ImageFile {
uri: string;
name: string;
type: string;
}
const [images, setImages] = useState([] as ImageFile[]);
//. using image picker.//
ImagePicker.showImagePicker(options, async (response) => {
if (response.didCancel) {
return;
}
else if (response.error) {
Alert.alert('에러: ' + response.error);
}
else {
//이미지가 무사히 선택된것
//기존에 있던 이미지에 더해서 배열을 더해나감
// console.log(response.data);
setImages([...images, { uri: response.uri, name: response.fileName, type: response.type }]);
}
console.log(images);
//post code//
async function toPost() {
let post: Post;
try {
post = await Post.create({ content: comment } as Post);
} catch (e) {
console.log(e);
console.log('포스트 생성 불가')
return;
}
try {
let a = 0
for (let image of images) {
console.log('image[0]: ' + JSON.stringify(image))
console.log('image[1]: ' + image.type)
console.log('test: ' + test)
await post.createMedia(test, 'IMAGE');
a++;
}
} catch (e) {
console.log(e.response.data);
console.log('이미지 생성 불가')
return;
}
try {
post.status = 'PUBLIC';
await post.save();
} catch (e) {
console.log(e);
console.log('포스트 저장 불가');
return;
}
console.log(`저장된 글: ${post.content}, 이미지 개수: ${post.medias.length}`)
}
and my api Code
public async createMedia(file, type: PostMediaType) {
const formData = new FormData();
formData.append('file', { uri: file.uri, name: file.name, type: file.type } as File);
const postMedia = new PostMedia((
await getAxios().post(`/posts/${this.id}/${type}s`, formData, { headers: { 'Content-Type': 'multipart/form-data' } })).data);
if (!this.medias) this.medias = await this.getMedias();
else this.medias.push(postMedia);
return postMedia;
}
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.
#ionic-native/file-transfer /file-opener not working I'm getting "open failed: EACCES (permission denied) error while trying to download a pdf and open in my ionic-angular project. This is the header files and function to open the attachment. This is working fine in android version 9 but not in android 10. What is the reason for this issue?
Header files
import { FileTransfer, FileTransferObject } from '#ionic-native/file-transfer';
import { File } from '#ionic-native/file';
import { FileOpener } from '#ionic-native/file-opener';
import { PhotoViewer } from '#ionic-native/photo-viewer';
Function call
constructor(public navCtrl: NavController, public navParams: NavParams,
...
private fileTransfer: FileTransfer,
private platform: Platform,
private file: File,
private fileOpener: FileOpener,
private photo: PhotoViewer,
...
) {...}
openAttachment(attachment) {
this.notification.getNotificationCount(this.userID).subscribe(res => this.setNotificationsCountAtStart(res));
this.loader.displayLoader();
const transfer: FileTransferObject = this.fileTransfer.create();
var filename = attachment.substring(attachment.lastIndexOf('/') + 1);
var filePath;
if (this.platform.is('ios')) {
filePath = this.file.documentsDirectory + filename;
} else if (this.platform.is('android')) {
filePath = this.file.externalRootDirectory + 'Download/' + filename;
}
if (attachment.indexOf('.pdf') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then((entry) => {
let url = entry.toURL();
this.fileOpener.open(url, 'application/pdf')
.then(() => {
console.log('File is opened');
this.loader.hideLoader();
})
.catch(e => console.log('Error opening file', JSON.stringify(e)))
}, (error) => {
// handle error
let toast = this.toast.create({
message: JSON.stringify(error),
duration: 3000,
position: 'bottom'
});
toast.present();
});
} else if (attachment.indexOf('.png') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else if (attachment.indexOf('.jpg') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else if (attachment.indexOf('.jpeg') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else {
this.loader.hideLoader();
}
}
I've changed the AndroidTargetSDK version to 28 in AndroidManifest file. Now its working. I'm running the app from AndroidStudio. So changing the value to 28 makes it working. Hope this works.
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);