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

🔥[Android][Storage] Upload to firebase storage from android internal folder fails #2558

Closed
2 of 10 tasks
eduardoprado opened this issue Sep 8, 2019 · 51 comments
Closed
2 of 10 tasks
Labels
Type: Stale Issue has become stale - automatically added by Stale bot

Comments

@eduardoprado
Copy link

Issue

Description

🔥 So my issue is really similar (if not the same) to this one. So i'm using this library so the user can select or take a picture to display in the app and then upload it to the firebase storage.

The issue occurs in Android when the user selects a picture from a place different than the default photo gallery, then when it tries to upload the uri to the firebase storage it outputs an error. This error doesn't happen when the user takes a picture or select the gallery of the device.

In iOS this problem doesn't happen as well because the user can only select a picture from the default gallery (or take a picture) when using the image picker component in this system.

This is my code to uploading the uri to the firebase storage:

async uploadPicture(picturePathUri: string): Promise<void> {
    console.log(`PATH: ${picturePathUri}`);
    try {
      await firebase
        .storage()
        .ref('Pictures/')
        .putFile(
          picturePathUri,
        );
    } catch (error) {
      console.log(error, JSON.stringify(error, null, 2));
    }
}

When the given uri is from the camera or the users' gallery (such as this one: file:///Users/Library/Developer/CoreSimulator/Devices/B27D6137-7BF5…F-397B-4439-BD5A-73350751C834/tmp/6B4587BC-7103-4577-BC2B-C16ABF5A9F6E.jpg) the putFile works fine and uploads it to firebase storage.

However if the uri is from a different place in the android file system (such as this one content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F78/ORIGINAL/NONE/1779395513) the putFile stops working and returns an error.

This is the error it outputs:

Error: An unknown error has occurred.
    at createErrorFromErrorData (NativeModules.js:155)
    at NativeModules.js:104
    at MessageQueue.__invokeCallback (MessageQueue.js:414)
    at MessageQueue.js:127
    at MessageQueue.__guard (MessageQueue.js:314)
    at MessageQueue.invokeCallbackAndReturnFlushedQueue (MessageQueue.js:126)
    at e (RNDebuggerWorker.js:1) {
  "framesToPop": 1,
  "nativeStackAndroid": [
    {
      "methodName": "createException",
      "lineNumber": 1942,
      "file": "Parcel.java"
    },
    {
      "methodName": "readException",
      "lineNumber": 1910,
      "file": "Parcel.java"
    },
    {
      "methodName": "readExceptionFromParcel",
      "lineNumber": 183,
      "file": "DatabaseUtils.java"
    },
    {
      "methodName": "readExceptionWithFileNotFoundExceptionFromParcel",
      "lineNumber": 146,
      "file": "DatabaseUtils.java"
    },
    {
      "methodName": "openTypedAssetFile",
      "lineNumber": 698,
      "file": "ContentProviderNative.java"
    },
    {
      "methodName": "openTypedAssetFileDescriptor",
      "lineNumber": 1458,
      "file": "ContentResolver.java"
    },
    {
      "methodName": "openAssetFileDescriptor",
      "lineNumber": 1295,
      "file": "ContentResolver.java"
    },
    {
      "methodName": "openFileDescriptor",
      "lineNumber": 1148,
      "file": "ContentResolver.java"
    },
    {
      "methodName": "openFileDescriptor",
      "lineNumber": 1102,
      "file": "ContentResolver.java"
    },
    {
      "methodName": "<init>",
      "lineNumber": 120,
      "file": "com.google.firebase:firebase-storage@@17.0.0"
    }
  ],
  "userInfo": null,
  "code": "storage/unknown"

I thought the problem might be related to permissions in the device when accessing the image in that particular location, but i already asked (and checked) permission for writing and reading from the external storage. Also, the image picker component can display that image in the app, so it must have permission to access it.

This is my code for the image picker component:

private onImageTap = async () => {
    if (Platform.OS === 'android') {
      try {
        const cameraPermission = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA);
        const galleryPermission = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        );
        if (cameraPermission === PermissionsAndroid.RESULTS.GRANTED &&
          galleryPermission === PermissionsAndroid.RESULTS.GRANTED) {
          RNImagePicker.showImagePicker(this.options, response => {
            if (response.didCancel) {
              console.log('User cancelled image picker');
            } else if (response.error) {
              console.log('ImagePicker Error: ', response.error);
            } else {
              const image: ImageURISource = { uri: response.uri };
              this.updateImage(image);
            }
          });
        }
        if (cameraPermission === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN &&
          galleryPermission === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
          this.props.onDeniedPermission(FlashMessageText.Both);
        } else if (cameraPermission === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
          this.props.onDeniedPermission(FlashMessageText.Camera);
        } else if (galleryPermission === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
          this.props.onDeniedPermission(FlashMessageText.Gallery);
        }
      } catch (err) {
        console.log(err);
      }

    } else {
      console.log('IOS device found');
      RNImagePicker.showImagePicker(this.options, response => {
        if (response.didCancel) {
          console.log('User cancelled image picker');
        } else if (response.error) {
          console.log('ImagePicker Error: ', response.error);
        } else {
          const image: ImageURISource = { uri: response.uri };
          this.updateImage(image);
        }
      });
    }
  }

Project Files

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
platform :ios, '9.0'

target 'App' do

  # Pods for App
  pod 'Firebase/Core', '~> 6.2.0'
  pod 'Firebase/RemoteConfig', '~> 6.2.0'
  pod 'Firebase/Messaging', '~> 6.2.0'
  pod 'GoogleIDFASupport', '~> 3.14.0'
  pod 'Firebase/Database', '~> 6.2.0'
  pod 'Firebase/Storage', '~> 6.2.0'

  target 'AppTests' do
    inherit! :search_paths
    # Pods for testing
  end

end

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

 subprojects { subproject ->
    afterEvaluate {
        if (subproject.hasProperty("android")) {
            android {
              compileSdkVersion rootProject.ext.compileSdkVersion
              buildToolsVersion rootProject.ext.buildToolsVersion
            }
        }
    if (subproject.name.contains('react-native-image-picker')) {
        buildscript {
            repositories {
                jcenter()
                maven { url "https://dl.bintray.com/android/android-tools/"  }
            }
        }
    }

android/app/build.gradle:

    implementation project(':react-native-image-picker')
    implementation project(':react-native-firebase')
    implementation "com.google.android.gms:play-services-base:16.1.0"
    implementation "com.google.firebase:firebase-core:16.0.4"
    implementation "com.google.firebase:firebase-messaging:17.3.3"
    implementation "com.google.firebase:firebase-database:17.0.0"
    implementation "com.google.firebase:firebase-storage:17.0.0"

android/settings.gradle:

include ':react-native-firebase'
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')

MainApplication.java:

import io.invertase.firebase.RNFirebasePackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
import io.invertase.firebase.database.RNFirebaseDatabasePackage;
import io.invertase.firebase.storage.RNFirebaseStoragePackage;
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
import com.imagepicker.ImagePickerPackage;

        new RNFirebasePackage(),
        new RNFirebaseMessagingPackage(),
        new RNFirebaseNotificationsPackage(),
        new RNFirebaseAnalyticsPackage(),
        new RNFirebaseDatabasePackage(),
        new RNFirebaseStoragePackage(),
        new RNFirebaseCrashlyticsPackage(),
        new ImagePickerPackage(),

AndroidManifest.xml:

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>


Environment

Click To Expand

react-native info output:

   React Native Environment Info:
    System:
      OS: macOS 10.14.2
      CPU: (8) x64 Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz
      Memory: 337.24 MB / 16.00 GB
      Shell: 5.6.1 - /usr/local/bin/zsh
    Binaries:
      Node: 10.7.0 - /var/folders/3r/_4_v7wyj34363lbms3vmx5wr0000gp/T/yarn--1567971805277-0.009550600204240034/node
      Yarn: 1.15.2 - /var/folders/3r/_4_v7wyj34363lbms3vmx5wr0000gp/T/yarn--1567971805277-0.009550600204240034/yarn
      npm: 6.1.0 - ~/.nvm/versions/node/v10.7.0/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.1, macOS 10.14, tvOS 12.1, watchOS 5.1
    IDEs:
      Android Studio: 3.1 AI-173.4697961
      Xcode: 10.1/10B61 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.8.6 => 16.8.6 
      react-native: 0.59.8 => 0.59.8 
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • 5.5.3
  • Firebase module(s) you're using that has the issue:
    • implementation "com.google.firebase:firebase-storage:17.0.0
  • Are you using TypeScript?
    • Y


@eduardoprado eduardoprado changed the title Upload to firebase storage from android internal folder fails 🔥 Upload to firebase storage from android internal folder fails Sep 8, 2019
@eduardoprado eduardoprado changed the title 🔥 Upload to firebase storage from android internal folder fails Upload to firebase storage from android internal folder fails Sep 8, 2019
@eduardoprado eduardoprado changed the title Upload to firebase storage from android internal folder fails 🔥[Android][Storage] Upload to firebase storage from android internal folder fails Sep 8, 2019
@mikehardy
Copy link
Collaborator

Smells like a FileProvider issue since you're failing on a content URI. This is a separate project but examine this: https://github.com/mikehardy/react-native-update-apk#manual-steps-for-android

@agungkes
Copy link

This is not related to firebase storage, I also use firebase storage and image-picker together

To process the uri on my Android, I use another library (ex: rn-fetch-blob, react-native-fs) so that the uri obtained from the original image picker (content: //) will be made into a file: //

@mikehardy
Copy link
Collaborator

@agungkes I don't believe that is recommended in android anymore - attempting to convert a content URI to a file URI. The ContentResolver system is specifically in place to abstract content from the file system so that other more ephemeral content providers can also provide images etc, such as Google Drive etc. Or so that permissions can be adequately checked, for instance in Android 10 you can have "external read access" as a permission but because of scoped storage might still be denied access to a file if you reach for it directly, whereas from a Content URI you might have permission.

@mikehardy
Copy link
Collaborator

@agungkes Some background reading - if you look at the comments on all the proposed solutions it's clear things work sometimes and not others, and that's because there are lots of cases where there is no "file", so you can't get a file path - in other words, you need to handle content paths somehow https://stackoverflow.com/questions/5657411/android-getting-a-file-uri-from-a-content-uri?noredirect=1&lq=1

@pierroo
Copy link

pierroo commented Sep 23, 2019

@mikehardy I'm sorry to bring back this thread, but app crashing on android when using putFile seems so random.

I was never able to reproduce the issue on my android devices, but I have hundreds of users who face this problem. (very small ratio, but still)

I have tried all kind of "handling content paths" without success (playing with the "file://" extension, adding or removing it etc)

It still crashes on their device and I am unable to find a permanent fix for all devices.

Has anyone found a good fix that work for all cases on android?

That would be amazing.

@mikehardy
Copy link
Collaborator

If you're still using file:// URIs at all, I would suspect they've chosen a data source that cannot be converted to file:// and you must use content resolver and the more virtual content:// URIs. That's all I can think of. @pierroo you don't give any information on your versions, the code in use, the error or anything though, so 🤷‍♂ just guessing here

@ffqs
Copy link

ffqs commented Sep 25, 2019

Okay guys. I've spent 3 days to resolving this problem. Actually react-native-firebase storage doesn't understand uri as "content://com.android.externalstorage". The real path should be given to file as "/data/emulated/0". Fetch this can eg use rn-feth-blob fs.state(fileUri)

@pierroo
Copy link

pierroo commented Sep 25, 2019

Okay gays. I've spent 3 days to resolving this problem. Actually react-native-firebase storage doesn't understand uri as "content://". The real path should be given to file as "/data/emulated/0". Fetch this can eg use rn-feth-blob fs.state(fileUri)

I'm not sure to understand, so using rn-feth-blob fs.state(fileUri) would ALWAYS return the correct path for filePut to handle on android? Nothing to worry about anymore using this method?

@ffqs
Copy link

ffqs commented Sep 25, 2019

Okay gays. I've spent 3 days to resolving this problem. Actually react-native-firebase storage doesn't understand uri as "content://". The real path should be given to file as "/data/emulated/0". Fetch this can eg use rn-feth-blob fs.state(fileUri)

I'm not sure to understand, so using rn-feth-blob fs.state(fileUri) would ALWAYS return the correct path for filePut to handle on android? Nothing to worry about anymore using this method?

I've tested this on google drive, sd-card, internal storage, cyrillic name, and not have problems

@pierroo
Copy link

pierroo commented Sep 25, 2019

@ffqs awesome, that sounds promising.

Would it be possible for you to share your piece of code where you use this method before calling putFile?

Do you use it for both iOS and android, or only android?

@ffqs
Copy link

ffqs commented Sep 25, 2019

Don't need for ios, just fileUri

@pierroo
Copy link

pierroo commented Sep 25, 2019

@ffqs got it.

So, could you please share the code you used exactly with putFile?

all examples I found on internet used the "put" method that does not exist with react native firebase.

@ffqs
Copy link

ffqs commented Sep 25, 2019

async function getPathForFirebaseStorage (uri) {
  if (IS_IOS) return uri
  const stat = await RNFetchBlob.fs.stat(uri)
  return stat.path
}

...

  const fileUri = await getPathForFirebaseStorage(file.uri)
  const uploadTask = ref.putFile(fileUri)

@pierroo
Copy link

pierroo commented Sep 25, 2019

async function getPathForFirebaseStorage (uri) {
if (IS_IOS) return uri
const stat = await RNFetchBlob.fs.stat(uri)
return stat.path
}

Thank you @ffqs , I will give it a try right now and let you know.

So you don't need the usual blob polyfill:

const Blob = RNFetchBlob.polyfill.Blob
const fs = RNFetchBlob.fs
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
window.Blob = Blob

?

I didn't know about this "RNFetchBlob.fs.stat" method, I have to look into it.

Thank you for your time!

@ffqs
Copy link

ffqs commented Sep 25, 2019

putFile already uploading file

@pierroo
Copy link

pierroo commented Sep 25, 2019

I come with bad news, unfortunately I sent a modified apk with this new RNFetchBlob method, but still no success, their app crashes when calling the putFile... Back to point zero.

@ffqs
Copy link

ffqs commented Sep 25, 2019

I come with bad news, unfortunately I sent a modified apk with this new RNFetchBlob method, but still no success, their app crashes when calling the putFile... Back to point zero.

Please more info. I use "react-native-firebase": "5.5.6". Also rn-fetch-blob was forked for support sd-card and google drive https://github.com/ffqs/rn-fetch-blob#46c672507c497154f2daeded433abb86ee7e3980

@pierroo
Copy link

pierroo commented Sep 25, 2019

I also use react-native-firebase 5.5.6, with latest rn-fetch-blob, and yes it does work for me and 99% of users.

But for some devices, it just crashes and I am still unable to know why: the imagePicker does show the image in the state taken from path or uri properly, but as soon as it comes to sending it to "putFile": it crashes.

It crashes on a Lenovo A7000-a, os version 5.0, and on a Primo R4 Plus, os version 5.1, for example.

@mikehardy
Copy link
Collaborator

The fork @ffqs is using is vital, it's doing the ContentResolver local cache file I was talking about, whereas the regular rn-fetch-blob does not do that in the base of the fork at least (maybe it's fixed in more current master) ffqs/rn-fetch-blob@9380242#diff-a46d0c3ac70a38fb8df8d56e9d80e121R211

@mikehardy
Copy link
Collaborator

I'm not sure the check it is doing is comprehensive though, I would expand it to any 'content://' URI, really ffqs/rn-fetch-blob@9380242#diff-a46d0c3ac70a38fb8df8d56e9d80e121R269

@pierroo
Copy link

pierroo commented Sep 25, 2019

The fork @ffqs is using is vital, it's doing the ContentResolver local cache file I was talking about, whereas the regular rn-fetch-blob does not do that in the base of the fork at least (maybe it's fixed in more current master) ffqs/rn-fetch-blob@9380242#diff-a46d0c3ac70a38fb8df8d56e9d80e121R211

But this contentResolver does not seem to be enough yet; is there no 100% bulletproof method to handle any kind of file path/uri users might be picking from imagePicker?

@mikehardy
Copy link
Collaborator

I think the bulletproof method would be to always do the local cache from a content:// uRI, like it is doing but only for google docs. Of course what level of bullet-proof? How do you handle failure if the device does not have enough space for the local copy? Might be easier to do a PR for react-native-firebase v5 that implements the v6 put command! esp since there is already an implementation to grab from v6.

@pierroo
Copy link

pierroo commented Sep 25, 2019

Regardless of very special cases like "not enough space for local copy", if such a method did exist I would be happy to implement it.

how to "always" do the local cache from a content uri? isn't it what the method of ffqs is supposed to do?

And even if they implemented the put method in v5; does it mean that as a safety measure android users should always rely on put method and forget the putFile method to make sure it will work for most?

@mikehardy
Copy link
Collaborator

Regardless of very special cases like "not enough space for local copy", if such a method did exist I would be happy to implement it.

how to "always" do the local cache from a content uri? isn't it what the method of ffqs is supposed to do?

Then check the second code link I posted where they check 'is it a google photos URL?' and instead say 'is it a content:// URL?' then if it is do the same thing I think

And even if they implemented the put method in v5; does it mean that as a safety measure android users should always rely on put method and forget the putFile method to make sure it will work for most?

A good point, yeah - I can see why doing putFile might be interesting in some cases? If you really know you have a file:// URL somehow? But I would never trust that a file on disk actually exists any more. Android has tried to move to all virtual streams of data provided on demand

@ffqs
Copy link

ffqs commented Sep 25, 2019

  1. Select file from Google Drive App (path: "content://com.google.android.apps.docs.storage.legacy"): Error RNFetchBlob "file not exist" (who would have thought)
  2. Select file from Google Drive App without RNFetchBlob (path: "content://com.google.android.apps.docs.storage.legacy"): Success
  3. Select file from phone file manager without RNFetchBlob (path: "content://com.android.externalstorage"): putFile failed
  4. Select file from phone file manager use RNFetchBlob stat (path: "content://com.android.externalstorage"): Success

uhh :(

@pierroo
Copy link

pierroo commented Sep 27, 2019

So, there is no way to 100% fix this photo issue on v5, is that what it means?

Migrating to v6 will be painful, but that is not the worst part, having to forget entirely about putFile and use put instead to handle all cases sounds bad.

Should we wait for firebase summit to be done before we bother the invertase team with this?

@mikehardy
Copy link
Collaborator

For unrelated reasons I was looking at rn-fetch-blob yesterday and there is a pull request that I personally reviewed that seems like it would fix things at that layer: joltup/rn-fetch-blob#165

So I would try: yarn add joltup/react-native-modal-datetime-picker#165/head as an rn-fetch-blob dependency, and then re-test something that gives you a virtual file (a content:// URI with no backing file, like from picasa or a contact photo or whatever) while using the code in that PR and see how you go.

As for the invertase team and timing, I'd say that's about right. But putFile exists in v6 https://invertase.io/oss/react-native-firebase/v6/storage/reference/reference#putFile - so you don't have to forget about it, and on a scan of that code it winds up here which says that it is content-uri safe: https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageReference.html#putFile(android.net.Uri,%20com.google.firebase.storage.StorageMetadata)

But then again I just checked the v5.x.x code thoroughly for comparison and it looks like it's doing roughly the same thing https://github.com/invertase/react-native-firebase/blob/v5.x.x/android/src/main/java/io/invertase/firebase/storage/RNFirebaseStorage.java#L376 - calling into the API I linked above which is supposed to be content-uri safe

So this seems pretty strange to me now, based on code inspection, this should be working, and moving to rnfbv6 probably won't affect it.

You could try the rn-fetch-blob PR I linked above, or investigate more deeply into why the access via content URIs is failing, but something's not right here and I'm leaning towards a permissions issue somehow - content resolver permissions are separate from external storage I believe, they are inter-app relationships, not file system, so maybe there is something there

@cdunkel
Copy link

cdunkel commented Oct 4, 2019

@mikehardy, just checking in on this issue. I'm in the process of upgrading to RN 0.61.1 paired with rnfb v5.5.6. Currently we're always having the user select a profile pic from the Android DocumentPicker, so we're getting the content:// uri.

In Gradle I'm referencing com.google.firebase:firebase-storage:17.0.0.

I'm not sure that I completely follow the conversation above, but do you recommend attempting to upgrade to rnfb v6.0.0, or trying to make the rn-fetch-blob work-around you mentioned work instead?

@mikehardy
Copy link
Collaborator

I can't decide for you @cdunkel as you may need notifications and that's not done for v6 yet. I have not personally tried what I mentioned above with the rn-fetch-blob PR but it seemed fine on review if you need RNFBv5 I'd use that PR

@cdunkel
Copy link

cdunkel commented Oct 5, 2019

@mikehardy, thanks! We're not using notifications through Firebase, so that shouldn't be an issue. I'll try the rn-fetch-blob PR first, though, and see if that solves my problem.

I did find something interesting yesterday while working on different ways to resolve this. I'm using the react-native-image-picker lib to allow the user to select a profile pic. They can take a new picture or select one from their library. If they take a new picture it still gives me a URI that starts with content://, but it successfully uploads. If they select one from their existing photos it fails.

@mikehardy
Copy link
Collaborator

you might be interested in the logcat output then, perhaps you are having a permission issue between the apps that the content resolver system is blocking you on

@cdunkel
Copy link

cdunkel commented Oct 7, 2019

@mikehardy I've reviewed my logcat output, as well as my RN console output and compared my success and fail cases. It appears that the primary difference is that when the upload succeeds it's coming from content://<my app package name>.provider/blah and when it fails it's coming from either content://com.android.providers.downloads.documents or content://com.google.android.apps.photos.contentprovider depending on if I'm using the DocumentPicker or ImagePicker libraries, respectively.

My current guess is that there's some issue with resolving the documents and photo providers into a real URI that can be uploaded to FirebaseStorage?

@ffqs
Copy link

ffqs commented Oct 7, 2019

my log from logcat, when i try upload file "latest-search" from SD-card (correctly for inner storage also) using only putFile

10-07 19:07:21.606 5147-5322/? E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/document/355E-19E2:Download/latest-search from pid=4971, uid=10220 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:709)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:516)
        at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:507)
        at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:452)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:313)
        at android.os.Binder.execTransact(Binder.java:462)

@mikehardy
Copy link
Collaborator

Yep - this verifies my hunch though I'm not sure exactly what to do about it, but it's inter-app permissions. For some reason the ContentResolver that has the content you want is not allowed to export it to you. Maybe you need Camera permissions on Android (I believe that allows access to camera roll) or external storage read (for the sd-card file) - although external storage is about to get a seismic shift as it goes to "scoped storage", a whole different thing...

@cdunkel
Copy link

cdunkel commented Oct 9, 2019

@mikehardy I was able to eventually get my code working. I had to use a combination of tactics to get file and image uploading working on both iOS and Android.

For uploading a document selected from storage on Android I'm using react-native-document-picker. Before showing the Document Picker I had to request READ_EXTERNAL_STORAGE permissions from the user using the react-native-permissions library. Once the user selects a file I'm using RNFetchBlob.fs.stat() to try to parse out the real path. If that succeeds (i.e. RNFetchBlob.fs.stat doesn't throw an error) then I call .put() and feed in stat.path as the url. If stat does fail, or the code is running on iOS, I just use the URI returned by DocumentPicker.

For image upload I'm effectively doing the same thing except that I'm using react-native-image-picker, and I have to use react-native-fs to parse out the real path using RNFS.stat(uri).

Regarding accessing the camera roll, I believe that I still needed external storage permissions to read that.

@mikehardy
Copy link
Collaborator

That all sounds about like what I'd expect. Not an awesome developer experience but matches my personal experience with Android file access - it's messy and yes - very permission based. At least react-native-permissions is really easy to work with. Glad you got it working!

@cdunkel
Copy link

cdunkel commented Oct 9, 2019

Thanks! Hopefully my experience can help @ffqs or anyone else find a solution that works for them.

@stale
Copy link

stale bot commented Nov 6, 2019

Hello 👋, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.

@stale stale bot added the Type: Stale Issue has become stale - automatically added by Stale bot label Nov 6, 2019
@stale
Copy link

stale bot commented Nov 21, 2019

Closing this issue after a prolonged period of inactivity. If this is still present in the latest release, please feel free to create a new issue with up-to-date information.

@stale stale bot closed this as completed Nov 21, 2019
@kenpham4real
Copy link

async function getPathForFirebaseStorage (uri) {
  if (IS_IOS) return uri
  const stat = await RNFetchBlob.fs.stat(uri)
  return stat.path
}

...

  const fileUri = await getPathForFirebaseStorage(file.uri)
  const uploadTask = ref.putFile(fileUri)

I've encountered this issue and have been stuck on this for 2 days in a row. Then I used RNFetchBlob like your approach. It works well. Thank you very much.

@traxx10
Copy link

traxx10 commented May 26, 2020

async function getPathForFirebaseStorage (uri) {
  if (IS_IOS) return uri
  const stat = await RNFetchBlob.fs.stat(uri)
  return stat.path
}

...

  const fileUri = await getPathForFirebaseStorage(file.uri)
  const uploadTask = ref.putFile(fileUri)

This works.
I was stuck for 3 hours

@phatmann
Copy link

phatmann commented Jun 4, 2020

The cleanest solution is to use react-native-fs:

import RNFS from 'react-native-fs'

const data = await RNFS.readFile(uri, 'base64')
await ref.putString(data, 'base64')

@mikehardy
Copy link
Collaborator

assuming that doesn't fill up available memory

@phatmann
Copy link

phatmann commented Jun 4, 2020

True. With content links you can only stream them. We need a streaming API to put to storage.

@meliodev
Copy link

I am using this as a work around but still, as @mikehardy affirmed, you need memory available :/

   if (response.uri.startsWith('content://')) {
                const uriComponents = response.uri.split('/')
                const fileNameAndExtension = uriComponents[uriComponents.length - 1]
                const destPath = `${RNFS.TemporaryDirectoryPath}/temporaryDoc`

                await RNFS.copyFile(response.uri, destPath)

                return destPath
            } 

@levino
Copy link

levino commented Dec 3, 2021

Thanks everyone. After a lot of try and error I was able to "fix" my issue. However most other libraries support the content:// paths and so should @react-native-firebase. Until then I use the following (async!) function on all paths I feed to putFile:

import RNFS from 'react-native-fs';
export const getFixedUri = async (uri: string): Promise<string> => {
    if (uri.startsWith('content://')) {
        const destPath = `${RNFS.TemporaryDirectoryPath}/tempVideo`;
        await RNFS.copyFile(uri, destPath);
        return destPath;
    }
    return uri;
};

Usage:

ref.putFile(await getFixedUri(path))

I have to use "tempVideo" as the file name since android gives me a weird substring 3A8 or so in the filename which is parsed by putFile as % and then things break apart. Of course this approach only allows one upload at a time but one could also append a timestamp or something to the filename.

@mikehardy
Copy link
Collaborator

Interesting - thanks for posting that @levino - it looks like it has an implied dependency on module react-native-fs? That's worth noting, as it's critical to your internalization workaround.
Handling ContentResolvers is more difficult then it seems - I work on a project that does so. I agree it's much more ergonomic for developers and would welcome a pull request that implemented them for the storage module here on Android

@komailabbas12
Copy link

komailabbas12 commented Feb 20, 2022

I apply all the solution above for Android 11 level 30. but i am still facing the same issue
file path i want to upload on firebase storage
file:///storage/emulated/0/Pictures/Screenshot/Website_Development.pdf
i also try
storage/emulated/0/Pictures/Screenshot/Website_Development.pdf

but still facing the same issue
open failed: EACCES (Permission denied)

kindly help me if you solved this for android 11. I already add the permission and manually allow that

<uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.MANAGE_DOCUMENTS"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

and i also add android:requestLegacyExternalStorage="true" but not working on android 11

@vikasswebdev
Copy link

Thank you so much @levino

@Bilal-Abdeen
Copy link
Contributor

Bilal-Abdeen commented Jun 20, 2022

If the problematic uri is coming from react-native-document-picker, I think the following solution is neater than the other solutions suggested so far (above.) However, it still has the limitation of the need for storage to copy the file - like most of the other solutions! The following solution does NOT need additional libraries, like react-native-fs or react-native-blob-util.
react-native-documents/document-picker#70 (comment)

@tsdmrfth
Copy link

@Bilal-Abdeen Thanks for the suggestion. It worked for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Stale Issue has become stale - automatically added by Stale bot
Projects
None yet
Development

No branches or pull requests