Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access Content uri in android #70

Closed
EyalSi opened this issue Sep 24, 2017 · 54 comments
Closed

Access Content uri in android #70

EyalSi opened this issue Sep 24, 2017 · 54 comments

Comments

@EyalSi
Copy link

EyalSi commented Sep 24, 2017

I'm getting a content uri in android:
'content://com.android.providers.media.documents/document/image%3A99'
Decoding it does not work, and it seems that it's impossible to access the file.
Does anyone has an idea how to access the file?

10x

@Elyx0
Copy link
Collaborator

Elyx0 commented Sep 27, 2017

Maybe here: https://stackoverflow.com/questions/3401579/get-filename-and-path-from-uri-from-mediastore

@aditya-simplecrm
Copy link

Any solution?

@dantman
Copy link
Collaborator

dantman commented Oct 24, 2017

URIs returned from Android are DocumentProviders URIs. You need to access them using a ContentResolver. react-native-fs cannot handle DocumentProvider documents, it's only designed to work with files inside the limited areas of the filesystem the app is permitted to access.

Right now react-native-fetch-blob might work, though it actually does a lot wrong so it may not. React Native is going to be getting Blob/DocumentProvider support in the future (0.50 or 0.51 hopefully), so it'll soon be possible to do things from within react-native.

@aditya-simplecrm
Copy link

Any alternate solution for file picker and upload to server?

@aditya-simplecrm
Copy link

I am able to get base64 from content uri using below code

RNFetchBlob.fs.readFile(this.state.pdf_uri,'base64')
// files will an array contains filenames
.then((files) => {
this.setState({base64Str:files})
// console.log(files)
})

I used this https://github.com/wkh237/react-native-fetch-blob package

@dantman
Copy link
Collaborator

dantman commented Oct 24, 2017

Yup, that could work for now, but keep in mind these problems with that library:
wkh237/react-native-fetch-blob#525 (comment)

@aditya-simplecrm
Copy link

@dantman Do you have solution of this problem?
I am new to react native.

@dantman
Copy link
Collaborator

dantman commented Oct 24, 2017

@aditya-simplecrm The "solution" I'm using in the app I'm working on is just to wait another month for RN to get native Blob uploading.

Alternatively if you feel like writing native code you can just write your own upload code in Java and Objective-C/Swift native code instead of JS.

@EyalSi
Copy link
Author

EyalSi commented Jan 11, 2018

There is now a solution for this problem in react-native-fs. Enabling access and copy content:// files -itinance/react-native-fs#395

@dantman
Copy link
Collaborator

dantman commented Mar 27, 2018

React Native 0.54.0 has finally been released with Blob support, you don't need any RNFS hacks. Now all we have to do is come up with some documentation on how to use it.

@AgNm
Copy link

AgNm commented Apr 5, 2018

@dantman can you please let me know how can we use it? Any document for same? I am using below version:

"react": "16.3.0-rc.0"
"react-native": "0.54.3"

@dantman
Copy link
Collaborator

dantman commented Apr 5, 2018

@AgNm Lookup the how to use fetch(). Fetch the content:// URL you get back from the document provider like you would any other URL. You should be able to get a Blob instance for the Body. The data for this Blob will be kept on the Native side of react. From there you should be able to use the normal web APIs for Blob instances (there may be some React Native specific behaviours you should look at in documentation or source code). I'm not sure what the API for getting the raw data out of the Blob is. But I do know that you can normally use a Blob as the body of a POST or probably the value of a FormData value. In other words, you should be able to fetch() a content:// URL and then upload the Blob you get back, and React Native will do this without ever sending whatever huge amount of data you downloaded over the slow Native<->JS bridge React Native uses.

@AgNm
Copy link

AgNm commented Apr 5, 2018

@dantman @Elyx0
I am using below code:

            const data = new FormData();
            data.append('name', file.fileName);
            data.append('photo', {
                  uri: uri_path,
                  type: file.type,
                  name: file.fileName
            });

            fetch(`${API.imageUpload}`, {
            method: 'post',
            body: data
            }).then(res => {
                console.log(JSON.stringify(res));
            });

Below is getting printed in console.log:

            {
                "type": "default",
                "status": 200,
                "ok": true,
                "statusText": 200,
                "headers": {
                    "map": {
                        "date": ["Thu, 05 Apr 2018 09:30:01 GMT"],
                        "content-length": ["2"],
                        "x-powered-by": ["ASP.NET"],
                        "x-aspnet-version": ["4.0.30319"],
                        "expires": ["-1"],
                        "content-type": ["application/json; charset=utf-8"],
                        "server": ["Microsoft-IIS/8.0"],
                        "pragma": ["no-cache"],
                        "cache-control": ["no-cache"]
                    }
                },
                "url": "",
                "_bodyInit": {
                    "listeners": {},
                    "isRNFetchBlobPolyfill": true,
                    "multipartBoundary": null,
                    "_ref": "/data/user/0/com.test/files/RNFetchBlob-blobs/blob-zzzqjeyyi7l2qml24yjhs2",
                    "_blobCreated": true,
                    "_closed": false,
                    "cacheName": "blob-zzzqjeyyi7l2qml24yjhs2",
                    "type": "text/plain",
                    "size": 2
                },
                "_bodyBlob": {
                    "listeners": {},
                    "isRNFetchBlobPolyfill": true,
                    "multipartBoundary": null,
                    "_ref": "/data/user/0/com.test/files/RNFetchBlob-blobs/blob-zzzqjeyyi7l2qml24yjhs2",
                    "_blobCreated": true,
                    "_closed": false,
                    "cacheName": "blob-zzzqjeyyi7l2qml24yjhs2",
                    "type": "text/plain",
                    "size": 2
                }
            }

I am not getting how to proceed further. Can you please help.

@dantman
Copy link
Collaborator

dantman commented Apr 5, 2018

@AgNm the /RNFetchBlob-blobs/ suggests you are using the unreliable react-native-fetch-blob package's fetch polyfill. Not the React Native 0.54 built-in fetch with Blob support.

Remove react-native-fetch-blob completely if you can. If not, see if you can bypass it and directly make use of the built-in fetch.

@regulus33
Copy link

URIs returned from Android are DocumentProviders URIs. You need to access them using a ContentResolver. react-native-fs cannot handle DocumentProvider documents, it's only designed to work with files inside the limited areas of the filesystem the app is permitted to access.

I'm not sure this is entirely true. Content uris are workable through many built in aspects of react native as(as of 0.53 anyway). For instance: CameraRoll.getPhotos() will return a collection of objects with content:// uris that are accessible and renderable from within React Native's Image component.

There is also no need to fetch this file locally and convert it to a blob before uploading. You can upload files at content:// uris using a properly formatted FormData object as a part of a multipart/form-data fetch.

Something like:

let data = new FormData()
data.append('image', {uri: 'content://path/to/content', type: 'image/png', name: 'name'})
const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      body: data
    })

works fine for me.
if your'e having trouble with a content:// uri make sure its a properly constructed one, a quick way to test is by pasting in your emulator's browser.

@jaeyow
Copy link

jaeyow commented Apr 30, 2018

@regulus33 this is absolutely correct, using a properly formatted FormData object as shown above will upload the file, without needing to convert the uri to a blob. I can confirm that this is correct as of RN v0.53.0

All React Native needs in the data.append call is the 2nd parameter with a uri attribute. See this comment from React Native FormData code :

// The body part is a "blob", which in React Native just means
// an object with a `uri` attribute. Optionally, it can also
// have a `name` and `type` attribute to specify filename and
// content type (cf. web Blob interface.)

However, to get it to work for me - I didn't need to specify 'Content-Type' in the header. If I set it to multipart-form-data like in the above example, I had issues with fetch not setting the boundaries of the files, which results in failed uploads.

So my fetch call is like this:

let data = new FormData()
data.append('image', {uri: 'content://path/to/content', type: 'image/png', name: 'name'})
const response = await fetch(url, {
      method: 'POST',
      body: data
    })

As an aside, if you want to do this in TypeScript, I had a past blog post about this.

@regulus33
Copy link

@jaeyow Great info! When in doubt, scour the source!

@smithaitufe
Copy link

What I am looking for is not uploading to a server. I want to be able to access the actual or real path that this library provides. Please is it possible to achieve this without first uploading to a remote server?

@dantman
Copy link
Collaborator

dantman commented May 20, 2018

@smithaitufe You cannot access the real path, the real path does not belong to your app on either OS. (On iOS you get a local copy, not the real path; On Android you get a content URI to access it through the document provider)

If you want to read the the data, you can fetch() the URI and use the Blob API to interact with the data.

@jkunwar
Copy link

jkunwar commented Jun 1, 2018

@dantman can you please provide an example for file upload.
i am using axios

@dantman
Copy link
Collaborator

dantman commented Jun 1, 2018

@jkunwar I haven't had a chance to use it in practice yet so I don't have an example. However I have confirmed that fetching a content:// URI from a picker worked. And others have mentioned that you can use content URIs directly with FormData instead of first fetching the URI to get a Blob.

Blob and FormData are standardized browser APIs, examples specific to the picker shouldn't be required.

Any tutorial or documentation for that should work:
http://shiya.io/using-fetch-to-upload-a-file/

@jkunwar
Copy link

jkunwar commented Jun 1, 2018

//choose file using document picker

async chooseFile() {
        try {
            const res = await DocumentPicker.pick({
                type: [DocumentPicker.types.allFiles],
            });
            this.setState({ file: res });
        } catch (err) {
            if (DocumentPicker.isCancel(err)) {
                 console.log(err)
            } else {
                throw err;
            }
        }
    }

//submit form data

 nextPressed() {
        var newFile = new FormData();
        newFile.append('title', this.state.value.title);
        newFile.append('tags', this.state.value.tags);
        newFile.append('category', this.state.value.category);
        if (this.state.value.status === true) {
            newFile.append('isPublic', 'public');
        } else {
            newFile.append('isPublic', 'private');
        }
        newFile.append('file', {
            uri: this.state.file.uri,
            type: this.state.file.type,
            name: this.state.file.name
        });
        this.props.addFile(newFile);
    }

//action to upload file

export const addFile = (file) => (dispatch) => {
    try {
        AsyncStorage.getItem('currentUser', (err, user) => {
            var userObj = JSON.parse(user);
            if (userObj.id) {
                const accessToken = userObj.id;
                dispatch({
                    type: FILE_ADDING,
                    payload: 'Adding file'
                })
                axios.post(`${ROOT_URL}/EpechaFiles/upload`,file, {
                    headers: {
                        'Authorization': `${accessToken}`,
                    }
                }).then(response => dispatch({
                    type: FILE_ADDING_SUCCESS,
                    payload: response.data
                })).catch(function (error) {
                    if (error.response) {
                        // The request was made and the server responded with a status code
                        // that falls out of the range of 2xx
                        dispatch({
                            type: FILE_ADDING_FAILURE,
                            payload: error.response.data.error
                        })
                    } else if (error.request) {
                        // The request was made but no response was received
                        // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
                        // http.ClientRequest in node.js
                        console.log(error.request);
                        dispatch({
                            type: FILE_ADDING_FAILURE,
                            payload: 'connection failed'
                        })
                    } else {
                        // Something happened in setting up the request that triggered an Error
                        console.log('Error', error.message);
                    }
                })
            } else {
                dispatch({
                    type: LOADING_FAILED,
                    payload: 'Please login and continue...'
                })
            }
        })
    } catch (error) {
        console.log("error retriveing store current user value on fileList.js , error: " + error);
    }
}

@dantman these are my codes to upload file. would you please look at it and tell me where i am going wrong

@dantman
Copy link
Collaborator

dantman commented Jun 1, 2018

@jkunwar I haven't used the FormData+uri mode yet, other people mentioned it, so I can't really help at the moment.

@jkunwar
Copy link

jkunwar commented Jun 1, 2018

@dantman thanks for the response

@jaeyow
Copy link

jaeyow commented Jun 1, 2018

@jkunwar what type of errors are you seeing?

@jaeyow
Copy link

jaeyow commented Jun 1, 2018

@jkunwar I haven't tried with axios + FormData, but I know this works with fetch + FormData. See this issue with React Native + axios + FormData (axios/axios#1321), and I am not sure if this is still an issue, and might be what you are seeing there..

@Preethyp
Copy link

Preethyp commented Jul 9, 2018

I've used RNFS to read file from content uri, but its returning encoded content. I want to send data to rails as multipart with axios. Suggest me to get file content from content uri without encoding.

@dantman
Copy link
Collaborator

dantman commented Jul 29, 2019

@tkow Blob data is stored in the native code area (don't remember if it's memory or cached in the filesystem). In order to get a base64 string, the entire contents of the file has to be transmitted over the Native<->JS bridge and converted into base64, which is a format that takes up more memory than the binary it represents. And because that base64 string is a JS string, the entirety of that data has to be in memory.

While ArrayBuffers may not have the wasted memory issue of base64, I don't believe there are any version of them that works the Blob way where data isn't handled by JS. So even if you can use them you still don't really want to use them because the real issue is that you do not want to transmit the file data over the Native<->JS bridge unless it's absolutely necessary.

That said if you absolutely have to process a large file in JS and transmitting data over the Native<->JS bridge you may want to look into blob.slice(...). That way you can split a file into smaller chunks and process the chunks one at a time. The whole file will still be sent over the bridge which will make this slow, but as long as you remove references to the previous chunk ASAP so the GC can clear it up and you never accumulate the whole file's data in memory you should be able to avoid an OOM.

For uploading, fetch supports Blob natively so you don't need to pass it over the bridge, you can just submit a fetch request with Blob in the body or FormData.

As for the issue where you want to take a Blob and save it to your app's filesystem. Unfortunately that is the one case where the RN ecosystem may need some more work. What you want there is for the native code to copy the file data directly from the native side of the Blob to the filesystem without ever sending the data over the Native<->JS bridge. Unfortunately I don't know of anything that did this when I was working with RNDP in the past. Ideally RNFS would have a method that would accept a Blob created by RNFS and copy/write it to a file. Which would then allow you to continue doing other things to it with RNFS. But I'm not sure whether that has ever been done.

@tkow
Copy link
Contributor

tkow commented Jul 30, 2019

@dantman

I appreciate to have your time.
I agree with you and RN blob should be used as formData now.

BTW, The reason why I want to cache files is that stable react-native-firebase supports cached files only.
In edge version, I found the blob conversion way in this code, and tested in same way.

https://github.com/invertase/react-native-firebase/blob/fe959bb8424724fbc7ec74f48d09d8a60fb046a7/packages/common/lib/Base64.js

but, this as previously I and you mentioned , inflate bytes and unreasonable processing bytes.
So, this won't work when reading large size blob of as same as my case.
Eventually, I will write some native bridge patch in my case. Thanks.

@sauravjmt
Copy link

@dantman
How to use content:// to send the file with email attachment ?
When i use the content:// as path i am getting 'couldn't attach the file' in mail app
Thanks

@dantman
Copy link
Collaborator

dantman commented Jul 30, 2019

@sauravjmt I have no clue what API you're even using to open the mail app.

@sauravjmt
Copy link

@dantman i am using 'react-native-mail' which is using Intent in android to send with email.

with following basic use

Mailer.mail({
subject: 'Testing',
recipients: ['],
ccRecipients: [],
bccRecipients: [],
body: 'A Bold Body',
isHTML: true,
attachment: {
path: , // The absolute path of the file from which to read data.
type: // Mime Type: jpg, png, doc, ppt, html, pdf, csv
name: // Optional: Custom filename for attachment
}
}, (error, event) => {
console.log("*******ERROR!!!===>",error)
console.log("*******Event!!!===>",event)
Alert.alert(
error,
event,
[
{text: 'Ok', onPress: () => console.log('OK: Email Error Response')},
{text: 'Cancel', onPress: () => console.log('CANCEL: Email Error Response')}
],
{ cancelable: true }
)
});

}

@dantman
Copy link
Collaborator

dantman commented Jul 30, 2019

@sauravjmt react-native-mail takes filesystem paths and on Android and then uses Uri.fromFile to make a file:// URI to pass in the intent (I guess they just assume that the app on the other end has permissions to read the whole filesystem?).

You will either have to copy the whole file to your app's cache folder and use that path. Or get react-native-mail to optionally accept URIs instead of paths and pass them on directly (assuming that the app on the other end has permissions to read them).

@catmans1
Copy link

My solution is create Android custom native module, and in React Native we call them with:
import { NativeModules } from 'react-native';

@sytolk
Copy link

sytolk commented Nov 15, 2019

How can I zip or unzip choosen file from document picker? https://github.com/mockingbot/react-native-zip-archive#api

if (res.type === 'application/zip') {
 let result = await fetch(res.uri);
 const blob = await result.blob(); //this works but how to save it and get file path from blob??

 const sourcePath = res.uri; // this not work it is content://..
 const targetPath = `${fs.DocumentDirectoryPath}/drivelog.cblite2`;
 const charset = 'UTF-8';

                unzip(sourcePath, targetPath, charset)
                    .then((path) => {
                        console.log(`unzip completed at ${path}`)
                    })
                    .catch((error) => {
                        console.log(error)
                    })
}

@demchenkoalex
Copy link

demchenkoalex commented Sep 18, 2020

Hi everyone, I have also encountered this problem and didn't find a solution here, so decided to write a small native module in Kotlin which is working in my case. If anyone has this problem you are welcome to try https://github.com/flyerhq/react-native-android-uri-path

[UPD]: See #70 (comment)

@djuwon
Copy link

djuwon commented Nov 22, 2020

Hi everyone, I have also encountered this problem and didn't find a solution here, so decided to write a small native module in Kotlin which is working in my case. If anyone has this problem you are welcome to try https://github.com/flyerhq/react-native-android-uri-path

I get an invalid synchronous error when using it while debugging. Do you have any idea why? It seems to pull the full path I want though

@demchenkoalex
Copy link

Hi @sawndur you can ask questions directly in my package, so we don't bother people here, but the short answer is - I didn't debug any code with this module In it, however if you will write me step by step what are you using - I will try to reproduce it and see if I can do anything. You can also play with the code yourself and make a PR if there are simple solution available.

@djuwon
Copy link

djuwon commented Nov 24, 2020

Hi @sawndur you can ask questions directly in my package, so we don't bother people here, but the short answer is - I didn't debug any code with this module In it, however if you will write me step by step what are you using - I will try to reproduce it and see if I can do anything. You can also play with the code yourself and make a PR if there are simple solution available.

I kind of made a workaround to suit my needs. Basically, the RNFetchBlob.fs.stat() wouldn't work because the URI getting received was not matching any of the conditions in the function so it just returned null. So I took your code and tweaked it to work in the stat() method and it worked like a charm. I'm getting full filepaths with file info such as size and name as well. I'm not too sure why it threw "Calling synchronous methods on native modules is not supported in Chrome" but it may have had to do with how I was calling it...

@Balthazar33
Copy link

Can the uri you get from DocumentPicker be used to open the file using Linking.openURL() ?
What I'm trying to achieve is to allow the user to select a file from the device, have it displayed on the screen, and when the user clicks on the file, they should be able to see it.

Trying to convert the uri to blob throws an error ( android.os.TransactionTooLargeException: data parcel size 10425488 bytes )

Is there a way to view the file using Linking.openURL( uri ) ?

@AhmedAli125
Copy link

You can simply pass another property copyTo: 'documentDirectory' in DocumentPicker.pick()/DocumentPicker.pickMultiple() function. You will receive the uri path of the object in fileCopyUri property of results. then you would simply need to iterate the results object and append 'file://' in fileCopyUri property in results elements, because android path start with file///. You also need to decode the path using decodeURIComponent. You can simply do it like this:

let results = await DocumentPicker.pickMultiple({
        type: [...documentType],
        copyTo: 'documentDirectory',
      });

results = results.map(item => ({
        ...item,
        fileCopyUri: `file://${decodeURIComponent(item.fileCopyUri)}`,
      }));

**Note: The android device only reads the path which is like this file:///. with 3 slashes **

@numandev1
Copy link

numandev1 commented Aug 31, 2021

you can also use this approach numandev1/react-native-compressor#25 (comment) this is same as copyTo approach

@networkException
Copy link

networkException commented Nov 21, 2021

Trying to fetch the fileCopyUri on Android (with file:// prepended) fails with TypeError: Network request failed (which comes from here), when logging the actual xhr error it says {"isTrusted": false}.

Is there anything I'm missing? Using the wrong fetch (no external library; react native v0.66.2)? Lacking some permissions?

I tried both 'documentDirectory' and 'cachesDirectory'.

The uri also does not look as different from the one emitted by react-native-image-picker:

Image picker: file:///data/user/0/com.APPNAME/cache/rn_image_picker_lib_temp_RANDOMSTRING.jpg
Document picker: file:///data/user/0/com.APPNAME/files/RANDOMSTRING/FILENAME.mp4 or file:///data/user/0/com.APPNAME/cache/RANDOMSTRING/FILENAME.mp4

Edit: It appears to be somewhat size related

@majirosstefan
Copy link

Yes, larger documents could not be fetched (using the fetch method), no matter if I am using the copyTo parameter or not
So, any ideas, how to 'fetch' bigger files ? (100MB+)? Looks quite strange to me, that this was created in 2017, and there are still some issues like this.

@networkException
Copy link

networkException commented Dec 24, 2021

I ended up reading the file manually using https://github.com/RonRadtke/react-native-blob-util#file-stream

The following configuration also solved content uri issues on Android and it also works on iOS:

const picked = await DocumentPicker.pickSingle({ mode: 'import' });
let path = picked.uri;

if (path.startsWith('file://')) path = path.substring('file://'.length);

const stream = await ReactNativeBlobUtil.fs.readStream(path, 'base64', chunkSize);

stream.open();
stream.onError(...);
stream.onData(...);
stream.onEnd(...);

@WAQAZ0178
Copy link

i want to upload audio file to firebase its give me error
even i store the uri,type, and name in state

error [Error: failed to stat path null because it does not exist or it is not a folder]

@gokujy
Copy link

gokujy commented Apr 20, 2022

I have the same issue with accessing media(content uri) files from an external app like music etc. No need to add anything just follow my answer.

I have fixed it by adding PermissionsAndroid from react-native

here is the code:

"react-native": "0.64.2"
"react-native-document-picker": "^7.1.3",

import {PermissionsAndroid, ToastAndroid} from 'react-native';

const uploadAudio = async finalSubmit => {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
      {
        title: 'Storage Permission',
        message: 'App needs access to memory to upload the file ',
      },
    );
    if (granted != PermissionsAndroid.RESULTS.GRANTED) {
      ToastAndroid.showWithGravity(
        'You need to give storage permission to upload the file',
        ToastAndroid.SHORT,
        ToastAndroid.BOTTOM,
      );
      return false;
    }
  };
  uploadAudio();

I hope this helps you :)

@vonovak
Copy link
Collaborator

vonovak commented May 26, 2022

I believe this thread has gotten so long there's little value in keeping the issue open.

If you're looking for a way to work with a file locally, eg. to upload it to a remote server, you'll need to use the fileCopyUri.

thank you

@kanika-webosmotic
Copy link

In case anybody is still facing this issue while using react-native-document-picker, Try adding " copyTo: 'cachesDirectory'" to parameter and use copyFileUri instead of uri. Below is an example of usage

const res = await DocumentPicker.pick({
allowMultiSelection: true,
type: [DocumentPicker.types.pdf],
copyTo: 'cachesDirectory'
});
const fileUrl = res.map((file) => file.fileCopyUri);
console.log({ fileUrl });

@Jhony-Reyes
Copy link

In case anybody is still facing this issue while using react-native-document-picker, Try adding " copyTo: 'cachesDirectory'" to parameter and use copyFileUri instead of uri. Below is an example of usage

const res = await DocumentPicker.pick({ allowMultiSelection: true, type: [DocumentPicker.types.pdf], copyTo: 'cachesDirectory' }); const fileUrl = res.map((file) => file.fileCopyUri); console.log({ fileUrl });

It was useful for me, thank you @kanika-webosmotic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.