Closed anshul0410 closed 2 years ago
Hi @anshul0410, at the moment there is no possibility. But I am currently working on integrating it in the lib. It's the next feature for the lib and it is already in work :) But I don't know when it will be done yet
Just wanting to give you a short update. I have the very basics working now (creating and writing data to media files), but there is still a lot of work to do. Especially to be able to use fileCache and such things.
@RonRadtke Is there any workaround until the solution is fully implemented? Our app needs invoices to be downloaded in a "common", non-app specific folder, like "Downloads" or "Documents" and we are not sure how we can implement this without your upcoming solution. Thanks!
@afilp in general it is working. The basics are done. So saving a file to downloads works.
But I had / have plenty of private issues eating up my time the last moths. So I didn't get to do the work on all the nice-to-have and testing like I would want to in order to make a release for it.
What doesn't work yet:
If you change to the feature/media_collections branch you could save a file by using the following code snippet. It's just copied from my testing app, so you might have to adjust variable names etc :)
ReactNativeBlobUtil
.config({
fileCache: true
})
.fetch('GET', targeturi, {'Accept': 'application/octet-stream'}, JSON.stringify(dat))
.then(async (res) => {
let pathnew = await ReactNativeBlobUtil.MediaCollection.createMediafile({name: filename, parentFolder: '', mimeType: get_mimetype_for_file(filename)}, 'Download');
await ReactNativeBlobUtil.MediaCollection.writeToMediafile(pathnew, res.path());
});
@RonRadtke Thanks for your work!
If I only want to download a PDF to the global downloads dir, it is safe to use the branch version? :) When do you expect the functionality to go live?
@damian-balas I tested the code in my companies app, works fine for saving a PDF to the download folder. But I'm not yet using it in production since I don't have the need for it yet. In general all errors should be caught and lead to a promise rejection as usual. So I think it should be save to use for that. But I would ofc recommend testing that it works as you expect.
I don't know yet. It depends a bit on how much of the additional things I program. I would say I skip the part about moving / renaming / ... for a first release. Any objections?
@RonRadtke Well it would be fine for me if you release a version where we can only download files to that dir. :)
@RonRadtke Would you also provide a snippet for iOS? I can't make it work... It gives me success status, but there are no files in the File.app
. I need to have it physically on the users phone so he can access it anytime.
@damian-balas iOS does not use the android mediaStore. For iOS you need
let destPath = ReactNativeBlobUtil.fs.dirs.DocumentDir + '/' + filename;
ReactNativeBlobUtil
.config({
fileCache: true
})
.fetch('GET', targeturi, {'Accept': 'application/octet-stream'}, JSON.stringify(dat))
.then(async (res) => {
await ReactNativeBlobUtil.fs.appendFile(destPath, res.path(), 'uri');
})
So for now you will have to put in a switch based on Platform.OS==='android'
.
Downloaddir on iOS doesn't work since there is no download folder like on android.
@RonRadtke Thanks a lot Ron! For me, just being able to download a PDF to the common Downloads
or Documents
folder would be sufficient.
Do we need to have conditionals before the call or the following code will work? (for both iOS and Android?)
I used to have this code in the main config
, so I thought the iOS would be OK because of the path
there:
appendExt: 'pdf', fileCache: true, path: destPath,
let dirs = ReactNativeBlobUtil.fs.dirs;
const dirToSave = Platform.OS === 'ios' ? dirs.DocumentDir : dirs.DownloadDir;
console.log('dirToSave', dirToSave);
const destPath = dirToSave + '/Invoice_' + paymentId + '.pdf';
// const date = new Date();
const { data } = await new Promise((resolve, reject) => {
try {
ReactNativeBlobUtil.config({
appendExt: 'pdf',
fileCache: true,
path: destPath,
addAndroidDownloads: {
path: destPath,
useDownloadManager: true,
title: 'Invoice ' + paymentId,
description: 'Invoice PDF',
mime: 'application/pdf',
mediaScannable: true,
notification: true,
},
})
.fetch('GET', `${window.our.BASE_URL}/payment?paymentId=${paymentId}`, {
Authorization: `Bearer ${token}`,
Accept: 'application/pdf',
'Content-Type': 'application/pdf',
})
.progress((received, total) => {
console.log('progress', received / total);
})
.then(async res => {
console.log('The file saved to ', res.path());
Alert.alert(t('invoiceDownloaded'));
if (Platform.OS === 'ios') {
await ReactNativeBlobUtil.fs.appendFile(
destPath,
res.path(),
'uri',
);
ReactNativeBlobUtil.ios.openDocument(res.path());
} else {
const android = ReactNativeBlobUtil.android;
android.actionViewIntent(
res.path(),
'application/pdf',
'Choose app to preview',
);
}
resolve(res);
})
// Something went wrong:
.catch((errorMessage, statusCode) => {
console.log(errorMessage);
reject(errorMessage);
});
} catch (err) {
console.log(err);
reject(err);
}
});
Also, I idid not see the progress
working, maybe because of the PDF?
@damian-balas iOS does not use the android mediaStore. For iOS you need
let destPath = ReactNativeBlobUtil.fs.dirs.DocumentDir + '/' + filename; ReactNativeBlobUtil .config({ fileCache: true }) .fetch('GET', targeturi, {'Accept': 'application/octet-stream'}, JSON.stringify(dat)) .then(async (res) => { await ReactNativeBlobUtil.fs.appendFile(destPath, res.path(), 'uri'); })
So for now you will have to put in a switch based on
Platform.OS==='android'
. Downloaddir on iOS doesn't work since there is no download folder like on android.
Thanks for your feedback. I'm aware of this, but my issue is that I can't see the downloaded file in the native File app. It's empty.
this is my code:
export const downloadPdf = async ({ url, filename }) => {
let response;
const newFilename = `${filename}-${Date.now()}`;
if (!url || typeof url !== 'string') {
throw new Error('Link do pliku PDF jest niepoprawny!');
}
try {
if (Platform.OS === 'ios') {
response = await downloadForIOS({ filename, url });
} else {
response = await downloadForAndroid({ filename, url });
}
showSuccessToast({
text: `${newFilename}.pdf został pobrany pomyślnie!`, // TODO: translate this!
});
} catch (error) {
showErrorToast({
error: {
message: `Nie udało się pobrać ${newFilename}.pdf. Spróbuj jeszcze raz.`, // TODO: translate this!
},
});
Logger.error(error);
} finally {
if (response?.flush) {
// response.flush();
}
}
};
const downloadForAndroid = async ({ filename, url }) => {
const result = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
if (result !== RESULTS.GRANTED) {
throw new Error(result);
}
const response = await ReactNativeBlobUtil.config({
fileCache: true,
}).fetch('GET', url);
const newPath =
await ReactNativeBlobUtil.MediaCollection.createMediafile(
{
name: `${filename}.pdf`,
parentFolder: '',
mimeType: 'application/pdf',
},
'Download',
);
await ReactNativeBlobUtil.MediaCollection.writeToMediafile(
newPath,
response.path(),
);
return response;
};
const downloadForIOS = async ({ filename, url }) => {
const path = ReactNativeBlobUtil.fs.dirs.DocumentDir + '/' + filename;
const response = await ReactNativeBlobUtil.config({
fileCache: true,
}).fetch('GET', url);
await ReactNativeBlobUtil.fs.appendFile(path, response.path(), 'uri');
return response;
};
The lib currently doesn't support the option to save it to files app. But you can set UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace To yes in yours app plist file. (Just take the link below and search for the first entry, there it is explained that itit should work... can't get a working link tonit on the phone sorry,) Or see https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW37
Thanks! Made it work with this code:
const downloadForIOS = async ({ filename, url }) => {
const path =
ReactNativeBlobUtil.fs.dirs.DocumentDir + '/' + filename + '.pdf';
const response = await ReactNativeBlobUtil.config({
fileCache: true,
appendExt: 'pdf',
path,
}).fetch('GET', url, {
Accept: 'application/pdf',
'Content-Type': 'application/pdf',
});
ReactNativeBlobUtil.ios.previewDocument(path);
return response;
};
But... the user needs to click: Save to Files
button :)
Edit 1: Your solution was better :) Thanks!!!
@afilp
Sorry just saw your post now.
In general you should be able to use the config, just that the useDownloadManager
might make problems, and the rest of the fields would not be respected by the write method for the mediastore.
And ofc you'll have to exchange the methods for writing the file on android.
So for your else part you need a await ReactNativeBlobUtil.fs.appendFile(destPath, res.path(), 'uri');
. The actionViewIntent does not work yet, as stated, not sure what the problem is.
I will fix it in a way that the config will be used. But currently I won't have the time for that.
@afilp @damian-balas And as soon as the config works and the opening of the files works I'll make a release for and by that push the version to 0.14.x If anyone of you has some time to help / test I would be very glad about it
@RonRadtke Thank you for your efforts!
I tried everything and still the file goes to the app's sandboxed storage and not to a common documents folder (note: I am not using your special branch, should I?).
I tried to make it work with your advice to check these 2 properties in plist to true
:
But still, res.path() and destpath are exactly the same, the app's one:
I tried all combinations, even having fileCache: false,
, still it does not work.
Also, this code only works once per session, it never works again for the next download:
ReactNativeBlobUtil.ios.openDocument(res.path());
Thanks a lot or all your efforts!
That's how storage in iOS works. Apps can only ever write in the sandbox storage. The only possibility you have is either saving it to the file app as you pointed out. The option I pointed out (setting the two flags) will make the files browsable by the files app and thus leads to the files show up in the files app. That's basically the only option you have if you don't want the user to have to save it again.
That's how storage in iOS works. Apps can only ever write in the sandbox storage. The only possibility you have is either saving it to the file app as you pointed out. The option I pointed out (setting the two flags) will make the files browsable by the files app and thus leads to the files show up in the files app. That's basically the only option you have if you don't want the user to have to save it again.
Is is secure to make those files visible?
Is is secure to make those files visible?
In general yes. Of course it is your responsibility to make sure there is no info available the user should not be able to access, like config files. Same it is your responsibility to make sure you don't store any malware etc there. But it is in general fine to use, the official developer docu of apple is describing it explicate so we can use it, it's not a hack or sth like that.
@RonRadtke Sorry if this is irrelevant, i am using react-native-image-picker
, how could I create a video file from this uri inside the same folder with the others? With appendFile
? Thank you!
where did you get the file from? It looks like it is a file from mediastore. Currently the library can't do anything with such URIs in means of loading the file to store it somewhere else e.g.
I used react-native-image-picker
and I just picked a video file. The type
and data
are what I get in the response (among other fields that I do not need to upload with the form).
This is the full response I get:
{
"height": 320,
"width": 568,
"type": "video/mp4",
"duration": 9,
"fileName": "video:45544",
"bitrate": 465939,
"fileSize": 566583,
"uri": "content://com.android.providers.media.documents/document/video%3A45544"
}
I thought I could use the fs
functionality to transfer the file
(is it a file or a link? What does content
mean?) in the same app-specific folder where the images are also copied.
For example, this is where the images are copied after picking them:
{
"height": 3000,
"width": 4000,
"type": "image/jpeg",
"fileName": "rn_image_picker_lib_temp_5992cea8-94e6-4071-81dc-d1593e1aa237.jpg",
"fileSize": 4586689,
"uri": "file:///data/user/0/com.company.app.debug/cache/rn_image_picker_lib_temp_5992cea8-94e6-4071-81dc-d1593e1aa237.jpg"
}
I cannot?
No currently not. The content URI is indicating that it is an uri from a content provider. So it's not a path it is a reference to a certain file. Basically you need to use a contentResolver and get the InputStream from that one, and with that stream you can write the file in the app specific directory. (Some MediaStore magic) And that is currently not implemented.
I know who to set a method up to do what you need. But I won't have time to do so until maybe Monday.
@RonRadtke Thanks a lot for your feedback.
We have an app in the store and Android (only) users cannot upload the "picked" videos, so if we could have a solution by Monday that would be great indeed!
Please note that I am not particularly interested to "copy" the video file to the "app-specific" folder (this was just a suggestion), I am interested for react-native-blob-util/
to just upload the file no matter where it is located, and no matter if it is a "content" or a "file" type. Of course, if intermediate custom steps are needed to be programmed by us (for example copying something somewhere), please let us know what to do and we will do them!
Thanks a lot!
Well, I can't promise I get it done on Monday. Depends a lot on how much spare time I'll have and how much I have to work / family. But I'll try.
Of course it would be possible without that intermediate step too. But I would like to have it as a separate module and don't mess the library up too much by having every functionality everywhere. Besides, on modern phones copying a single file really shouldn't be an issue storage wise.
Yes Ron, I understand.
FYI, I tried to do an fs.copyFile
logic and I have reached a wall...
err Error: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/video:45772 from pid=20415, uid=10641 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
The code I am using is this:
if (type === 'video' && Platform.OS === 'android') {
let dirs = ReactNativeBlobUtil.fs.dirs;
const dirToSave =
Platform.OS === 'ios' ? dirs.DocumentDir : dirs.DownloadDir;
try {
console.log('dirToSave', dirToSave);
const destPath = dirToSave + '/' + filename + '.' + typeForTheFilename;
debugger;
await fs.copyFile(decodeURIComponent(el.uri), destPath);
} catch (err) {
console.log('err', err);
}
const res = await fs.readDir(dirToSave);
debugger;
console.log('res', res);
}
I updated the code and I see that I do get true
for the READ_EXTERNAL_STORAGE
permission, so in theory I do have permission to read the file from there (or not?).
These are the Android permissions, I even added the "MANAGE_DOCUMENTS" to no avail:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
No you don't. Because the file does not belong to your app. I would recommend reading a bit about scoped storage. Basically by default you can only read / write within the scoped storage belonging to your app. And only your app, no other app and neither the user itself, has access to these files. So if you want access to a file somewhere else (e.g. in the images/videos in the gallery) first of all the user has to select the file actively (you can't just query a random file, except with file manager permissions) , and then you need to use certain functions to be allowed to read these files.
They changed the whole storage system with Android 10, you still have the "old" system in mind. But that's all the magic happening in java / kotlin you usually don't see when just programming your app in react-native.
Thanks Ron, so the problem with the "content" file will still be there unless you find a way for all of us to be able to get it as an actual file inside the app's scoped storage. I wish react-native-image-picker
had an option for us to save the actual file instead of providing a "content" link.
Meanwhile, I managed to write to the common "Downloads" folder using react-native-file-access
, which is the opposite direction (I needed this in order to be able to save a downloaded PDF there, using their fetch
). I wonder if this could be used somewhere in your library.
Thanks a lot!
@afilp to the downloads folder you can save with my library on the new branch... So there is no need for the additional library. So basically you don't need the changes anyways? But asking about it in the first place?
No you misunderstood me. They are 2 different problems.
I still have the major problem of "content" video files not being able to be uploaded to our server, because there is no way at the current master branch to get the actual file from a "content" uri.
My initial asking about saving to Downloads also still remains. I only mentioned the other library as I found it just 2 hours ago and gave it a try in case there is something there that may help you with the implementation/testing of your branch.
Thanks a lot!
Okay, thanks. Might help with my problem, that I have to use the cache file, but will see.
I wrote a new function copyToInternal which should do the work and copy the file to the apps internal storage. Just need to specify the contenturi and the targetpath. But it is untested and I don't have time to test it right now. So feel free to test it :D Otherwise I might get to it tomorrow or on monday.
Thanks a lot Ron, you are awesome!
The copyToInternal function can be used to also get the video file from the "content" uri in media store (in the format the picker gives) and upload it?
Or this is only for solving the initial problem with the "downloads"?
@afilp
Yes to the first.
This function will copy a file given by content URI (so the video you get from react-native-image-picker
) to the apps internal storage.
And after that being done you can use it like every other file in the apps storage (so like the images you get from react-native-image-picker
), and thus should be able to upload it.
So:
1) pick the image
2) copy it with await ReactNativeBlobUtil.MediaCollection.createMediafile(<contenturi>, <internal targetpath>)
3) send it to the sever as usual
WIth ReactNativeBlobUtil.fs.dirs.CacheDir
Saving to dowloads works too. For that you need sth like the following:
ReactNativeBlobUtil
.config({
fileCache: true
})
.fetch('GET', targeturi, {'Accept': 'application/octet-stream'}, JSON.stringify(dat))
.then(async (res) => {
let pathnew = await ReactNativeBlobUtil.MediaCollection.createMediafile({name: filename, parentFolder: '', mimeType: get_mimetype_for_file(filename)}, 'Download');
await ReactNativeBlobUtil.MediaCollection.writeToMediafile(pathnew, res.path());
});
Explanation:
ReactNativeBlobUtil.MediaCollection.createMediafile(...)
will create a new MediaStore file with the given name / subfolder / ...
ReactNativeBlobUtil.MediaCollection.writeToMediafile
will write the data from the fileCache (so res.path()) into the new media storage file
So with these two function calls after the fetch resolved you're able to write the file to the public downloads folder.
Thanks @RonRadtke
Meanwhile, I was reading a lot about this issue as many other users face it with pickers
and found a post that said the he used stat
and just took the actual file uri through this. I tried it and it did work (i.e. the actual video did upload):
const stat = await ReactNativeBlobUtil.fs.stat(el.uri);
el.uri = stat.path;
Was that just a lucky coincidence? Maybe because it was in Movies
?
I wonder if this can be a solution (no need to copy anything) or it will fail in many other cases/devices/Android versions/etc,
Thanks.
@afilp it works, but it might fail. A URI doesn't necessarily has to be a file or anything you can read or use. So it might work for all your cases since you come over the picker. But in general it is something that will sooner or later break. An other possibility that would likely would work would be to return a BLOB object containing the data of the file - that would skip the copy part too. But copying will do the cut for now too. Regardless getting the BLOB or so would be a nice feature in general.
So if you don't have a problem with, I would say we stick with the copying for now. At least that is the fastest way you will be able to use that functionality, since I don't have that much time currently (exams, work, family, ...) And I will keep working on the mediastore, add a few more functions and then release it officially. But till then, you can use the branch. Depending on how much time i have in spare I might be able to get the release within the next 2 weeks or so
Edit: To maybe clarify it ab bit. The content uri is basically a descriptor for a database entry. The stat method is aquering a cursor pointing on the row specified by the uri. And now you're hoping it is an entry for a file, but it could also be a collection of some sort, or what ever else android is storing there. Do it being a file isn't guaranteed and it might fail - or you simply get whatever but a path
Very interesting discussion. Thanks @RonRadtke for working on this repo. I ended up here as I am also facing the issue of saving files to the shared "Download" folder.
With the old package, I was simply using RNFetchBlob.fs.dirs.DownloadDir
(which returned /storage/emulated/0/Download
) as the target folder, and then copying (with RNFetchBlob.fs.cp(sourcePath, targetPath)
) files from the cache to the target.
In this package, ReactNativeBlobUtil.fs.dirs.DownloadDir
returns /storage/emulated/0/Android/data/<myapp>/files/Download
. After reading the comments here and in other forums, I was under the impression that this change was motivated by some change in the Android API, which would not allow to save to the shared Download folder anymore.
So I just quickly tested hardcoding the target to /storage/emulated/0/Download
and tried copying files from the cache to the target. It worked both on a physical device running Android 9, and on an emulator (Pixel 5) running Android 11.
So I am not quite sure why ReactNativeBlobUtil.fs.dirs.DownloadDir
doesn't return /storage/emulated/0/Download
as before. Am I misunderstanding something?
Note: I just use the standard permissions READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE in the manifest.
This should not work unless you're using preserveLegacyExternalStorage=true
.
It was changed because the way to aquire the paths was changed. The old methods ate flagged deprecated and the new return other values.
It came with one of the PRs I mergred after taking over the lib. But it is in general not possible to write there anymore, especially since the flag mentioned only works on targetapilevel < 30.
But as mentioned in one PR there would be the possibility to add a flag to get the old paths. Even though it won't help anyone targeting Android 11 (api 30), which is mandatory for all updates by now.
Oh you're right, I do have android:requestLegacyExternalStorage="true"
in the manifest. So I guess the MediaStore API is the best way to go from now on.
@afilp so copying to the app storage works. So basically all functionality you need is available on the feature branch. I also managed to fix the issue with opening a file downloaded to the downloads folder, so we're getting closer for a release - at least if we skip the whole part in the config with overwrite / fileCache=false / ....
Excellent! I am looking forward to the release and use it asap!
Also, remember that I tried meanwhile to solve the problem of just copying to a "public" folder with another library?
See here, while it works in Android, I have a problem in iOS, it still tries to write in a "non-public" (app-specific) Downloads folder. https://github.com/alpha0010/react-native-file-access/issues/41
I am only mentioning it so that you may get inspired from this issue and maybe your branch solves this problem too. Thanks!
@afilp as explained already. It's not possible. The only option to offer would be an action for the user to save it manually by himself. But there the user probably would also see all other options about sharing the file.
Sorry I have not realized this well, so the only way is to use the Share
method of react-native and hope that uses will find the "Save" option.
Yes, at least if you want it saved there and are not fine with it being shared and thus be visible there. I don't understand why you want to explicitly store it in downloads / documents and not just show it there. The user won't see the difference.
@RonRadtke Maybe I get this wrong, but when react-native-blob-util
downloads the PDF then we show an alert that says something like "PDF Downloaded". But then the user has no clue what to do next to get to the actual file. I wonder if there is a way to have a link to that particular folder in the FIles
app, so that the Files
app opens in that folder for the user to view the file immediately.
I thought that by being in a common Downloads folder that would be easier to find. At least in Android seems easier.
@afilp there are basically two options:
ReactNativeBlobUtil.ios.openDocument(destPath)
I did use both the previewDocument
and the openDocument
, like that:
if (Platform.OS === 'ios') {
ReactNativeBlobUtil.ios.previewDocument(res.path());
}
However, it only works the first time during the entire app's session. It does not appear the next times I download a document, the same or others. Is this a bug I assume? Have you noticed this behavior? I do not know why this happens.
Yes that is for sure a bug. If there is no issue yet, could you please create one for? So we don't have too much of a mess in this one here
Sure, here it is: https://github.com/RonRadtke/react-native-blob-util/issues/108
For info, I decided to go ahead for now with the cpExternal(sourcePath, fileName, dirType)
function from https://github.com/alpha0010/react-native-file-access, which uses the MediaStore API. dirType can be 'downloads', 'images', 'audio' or 'video'.
It pretty much fits my app's use case since user files are already downloaded and in the cache, and just need to be copied and made available outside of the app. Would be great if react-native-blob-util eventually had a similar function.
Hi, thanks for maintaining this project. i have more of a query than a bug to report. i'm using version 0.13.8 of this library and implementing scope storage related updates in my RN app. Is there a way to download files directly to common download path for android, currently file download is happening in app specific internal storage, which might not be very intuitive for users in general. Is there a plan to implement MediaStore API to make use of download path, or is there any other solution to do so? Any help or suggestion is much appreciated. Thanks.