RonRadtke / react-native-blob-util

A project committed to making file access and data transfer easier, efficient for React Native developers.
MIT License
746 stars 128 forks source link

MediaStore to use common downloads directory of android #62

Closed anshul0410 closed 2 years ago

anshul0410 commented 3 years ago

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.

RonRadtke commented 2 years ago

@alariej yep, is planned for this or next week :)

afilp commented 2 years ago

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?

image

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.

@RonRadtke I just tried it in a tablet, picking (with image-picker) an mp4 video from the Downloads folder which I downloaded previously from Google.

I get this error, is there any way to solve this? Because it will not get the actual file to download as video and we are again at ground zero with our app :-) :

image

Note: I even used decodeURIComponent(el.uri)

afilp commented 2 years ago

The strange thing is that the behavior is different (it works) in my Android phone, while both have Android 11 version.

I assume they have a different way to store the "Downloaded from Google" video file.

RonRadtke commented 2 years ago

@afilp it's released!

The issue with stat is what I described earlier. There is no guarantee for the row actually pointing to a file. And the issue you're seeing here is exactly that. There is no way to solve that except for switching to the newly introduced APIS

RonRadtke commented 2 years ago

I'll reopen the issue again - just in case you have feedback. If there are any new bugs - please create a new issue though

afilp commented 2 years ago

Thanks Ron!

Maybe you missed to type the related code here?

image

afilp commented 2 years ago

@afilp it's released!

The issue with stat is what I described earlier. There is no guarantee for the row actually pointing to a file. And the issue you're seeing here is exactly that. There is no way to solve that except for switching to the newly introduced APIS

With your new v0.14 we do not need to do anything else other than use it, correct? Not sure what you meant with except for switching to the newly introduced APIS. This is done on v0.14, correct? Or we have to manually do something more? Thanks!

afilp commented 2 years ago

Also, I tried to update the Wiki but I do not have update permissions:

From: let destpath = ReactNativeBlobUtil.dirs.CacheDir

To: let destpath = ReactNativeBlobUtil.fs.dirs.CacheDir

(there is a missing fs there, I believe)

Maybe you would like to change this.

RonRadtke commented 2 years ago

Thanks Ron!

Maybe you missed to type the related code here?

image

no, that's exactly want I meant to write. You get a database row (or multiple) from a query. But probably it would be better to write cursor instead of row, since we get a cursor not a single row.

@afilp it's released! The issue with stat is what I described earlier. There is no guarantee for the row actually pointing to a file. And the issue you're seeing here is exactly that. There is no way to solve that except for switching to the newly introduced APIS

With your new v0.14 we do not need to do anything else other than use it, correct? Not sure what you meant with except for switching to the newly introduced APIS. This is done on v0.14, correct? Or we have to manually do something more? Thanks!

You need to use the new functionality. it's not a replacement for the old one, it's an addition. So you will have to call the correct functions as described in the wiki.

afilp commented 2 years ago

Ι tested it in both my Android phone and tablet and worked perfectly!

Thanks a lot!

jaedontym commented 2 years ago

Hi @RonRadtke, thank you for working so actively on this library. It's been really useful for developers like me who aren't familiar with native Android code. You have no idea how happy I was to see v0.14 released this morning!

On that note, I've been trying to get v0.14 to work on Android 7 but I'm having issues. I'm thinking one of the issues might be in this method from ReactNativeBlobUtilMediaCollection.java (added my questions as comments in the code):

public static Uri createNewMediaFile(FileDescription file, MediaType mt) {
    ...

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
     ...
    } else {
        File directory = Environment.getExternalStoragePublicDirectory(relativePath);
        if (directory.canWrite()) {
            File f = new File(relativePath + '/' + file.getFullPath()); // <-- Instead of relativePath, should it be directory.getPath() instead? Right now this seems to be causing the createNewMediaFile method to return null
            if (!f.exists()) {
                boolean result = f.mkdirs(); // <-- Should this be f.createNewFile() instead? Right now it's creating a folder
                if (result) return Uri.fromFile(f);
            }

        }
    }

    return null;
}

I tried replacing making the following replacements and the file got created successfully in the public downloads folder:

However this led to another problem in the writeToMediaFile() method:

Screenshot 2022-02-07 at 11 22 23 AM

Here's a relevant snippet of my React Native code:

   ReactNativeBlobUtil
      .config({
        fileCache: true,
        path: `${Platform.OS === 'ios'
          ? ReactNativeBlobUtil.fs.dirs.DocumentDir
          : ReactNativeBlobUtil.fs.dirs.DownloadDir
        }/${filename}`,
      })
      .fetch('GET', url, {...})
      .then(async (res) => {
        if (Platform.OS === 'ios') {
          ...
        } else {
          const result = await ReactNativeBlobUtil.MediaCollection.copyToMediaStore(
            {
              name: 'TestFile',
              parentFolder: '',
              mimeType: 'application/pdf',
            },
            'Download',
            res.path(),
          );
        }
      });

With this implementation, the file gets saved properly to the app's scoped "Download" directory but doesn't get copied to the shared storage "Download" directory.

I'm not sure if this is considered a new bug. If it is, I'll create a new issue as requested.

RonRadtke commented 2 years ago

Hi @jaedontym, thank you very much for appreciating my work here. It really helps to now people do like and sue my work. And thanks for the bug report. You're partly right. I absolutely forgot to actually create the file. I'm currently only creating the parent directories, but we have to create these too, we can't just only create the file. This will otherwise lead to an error if the parent directory doesn't exist. I am working on a fix for it, I just want to test it first since I did some more clean up and adding that the write method works for older APIs too.

jaedontym commented 2 years ago

@RonRadtke Thank you so much. Can't wait to integrate the updates to get my app working properly with the newer Android versions! I'll continue reporting relevant bugs here as I find them :)

afilp commented 2 years ago

@RonRadtke Just to make sure, the pickers store the files in the "Cache", right? So this gets bigger and bigger? Or is it automatically cleaned when closing the app, etc.?

image

I am asking in case I have to manually delete the selected images/videos after the user uploads them to the server, using react-native-blob-util.

If yes, should I use this method of yours? ReactNativeBlobUtil.fs.unlink or ReactNativeBlobUtil.fs.mv ?

Thanks!

RonRadtke commented 2 years ago

Yes that will grow. Unlink would be the way to go.

HagarAyad commented 2 years ago

I'm facing problem with copyToMediaStore , I get this error : Failed to get output stream here is my code const result = await ReactNativeBlobUtil.MediaCollection.copyToMediaStore( { name: eventTitle, parentFolder: '', mimeType: 'application/pdf', // MIME type of the file }, 'Download', ${filePath}, ); and filePath is : /storage/emulated/0/Android/data/com.runofshow/files/Download/Test.pdf

afilp commented 2 years ago

Yes that will grow. Unlink would be the way to go.

I tried this, but when I reload the app I still see the photo there in the UI (it should have been a "missing image").

        formData.forEach(el => {
          if (el.origPath) {
            ReactNativeBlobUtil.fs.unlink(el.origPath);
          }
        });

This is the code for the origPath:

      formData.push({
        name,
        filename,
        type: el.type,
        origPath: el.uri,
        // Change BASE64 encoded data to a file path with prefix `ReactNativeBlobUtil-file://`.
        // Or simply wrap the file path with ReactNativeBlobUtil.wrap().
        data: ReactNativeBlobUtil.wrap(
          decodeURIComponent(
            Platform.OS === 'ios' ? el.uri.replace('file://', '') : el.uri,
          ),
        ),
      });

Is unlink working? (it does not seem so)

UPDATE: I do see the photo in the UI, but when trying to re-upload it it shows as missing. So why the UI shows it while it is not there?

afilp commented 2 years ago

What type is the response data, is it always a boolean? (could not find that in docs) I am using JSON.parse as I though it can also be an object and I need to return it back to react-query:

image

So, what should we expect for "data" there? Thanks! (on uploading success I mean)

RonRadtke commented 2 years ago

UPDATE: I do see the photo in the UI, but when trying to re-upload it it shows as missing. So why the UI shows it while it is not there?

Which UI?

So, what should we expect for "data" there? Thanks! (on uploading success I mean)

Whatever your server sends. The data is the raw value your server is sending, or, if your config is specifying to store it in a file, the path. So depending on what your goal is, it might be better to use the text() or json() since this will resolve a file path and return the data sent by the server.

jaedontym commented 2 years ago

@RonRadtke Hi Ron! How are the fixes coming along? I hope that no additional complications arose 😰
I went ahead with the app internal storage implementation first but this is affecting the UX of downloading and uploading files quite a bit (most users don't know how to navigate to the directory). Fortunately that's not the app's main feature but I do hope to be able to get this sorted out in time.

As always, really appreciate your work to keep rn-fetch-blob alive and up-to-date even with your busy schedule!

RonRadtke commented 2 years ago

@jaedontym sorry I had 2 exams and some private issues so I got kinda delayed. But I think I got it fixed, I will run some more tests tomorrow and if these are fine I'll craft a new release.

jaedontym commented 2 years ago

@RonRadtke I hope your exams went smoothly and your private issues get resolved. Take care, Ron 🙏

RonRadtke commented 2 years ago

@jaedontym Update is released

RonRadtke commented 2 years ago

@HagarAyad the update I released yesterday might fix your problem too. I found a way to reproduce it and fixed it - but I of course can't promise there isn't anything else wrong (e.g. another app modifying the file or so) @jaedontym thanks! The function should now work as expected and also work on android < 10. With the next update I plan to bring back the old environment paths that should enable being able to write to the public download folder on old devices agian. @afilp how's it going? everything working well with that part of the lib?

afilp commented 2 years ago

@RonRadtke Yes, all seems well in Android now! I did not even need that fix, most likely because our users have recent Android versions, but of course it is nice to have this too!

My other (unrelated) problem is when users pick in iOS many heavy images through the picker (8MB each for example) and I show them in a Flatlist. When there are 15+ of them, I have reports that the app is crashing when scrolling to the end of the list.

If you happen to know how to cope with this issue I will greatly appreciate it! I am thinking of using react-native-resizer, create duplicate smaller images as thumbnails, to show these in Flatlist, while using react-native-blob-util to upload the original large images one-by-one. But all this is a hassle, Android does not crash, not sure if there is another way in iOS.