ivpusic / react-native-image-crop-picker

iOS/Android image picker with support for camera, video, configurable compression, multiple images and cropping
MIT License
6.12k stars 1.56k forks source link

Starting May 5th, you must let us know why your app requires broad storage access #1571

Open diegolmello opened 3 years ago

diegolmello commented 3 years ago

Version

Tell us which versions you are using:

Platform

Tell us to which platform this issue is related

From Google Play inbox:

We've detected that your app contains the requestLegacyExternalStorage flag in the manifest file of 1 or more of your app bundles or APKs.

Developers with apps on devices running Android 11+ must use Scoped Storage to give users better access control over their device storage. To release your app on Android 11 or newer after May 5th, you must either:

Update your app to use more privacy friendly best practices, such as the Storage Access Framework or Media Store API Update your app to declare the All files access (MANAGE_EXTERNAL_STORAGE) permission in the manifest file, and complete the All files access permission declaration in Play Console from May 5th Remove the All files access permission from your app entirely For apps targeting Android 11, the requestLegacyExternalStorage flag will be ignored. You must use the All files access permission to retain broad access.

Apps requesting access to the All files access permission without a permitted use will be removed from Google Play, and you won't be able to publish updates.

https://support.google.com/googleplay/android-developer/answer/10467955

Do we have a workaround for that?

Thanks!

Love react-native-image-crop-picker? Please consider supporting our collective: πŸ‘‰ https://opencollective.com/react-native-image-crop-picker/donate

phatlaunchdeck commented 3 years ago

I got this message too and would like to know if anyone can come up with a solution

aj3012 commented 3 years ago

+1

jacquesdev commented 3 years ago

I suspect that some people get that warning after using the suggested fix here https://github.com/ivpusic/react-native-image-crop-picker/issues/1449#issuecomment-724638956, not sure if it affects everyone though?

andresribeiro commented 3 years ago

+1

Steffi3rd commented 3 years ago

I suspect that some people get that warning after using the suggested fix here #1449 (comment), not sure if it affects everyone though?

Yes, indeed. (for me)

proactivebit commented 3 years ago

I do research about it and the waring is applicable to case when you are targeting level 30 Android 11 I think. If you target level 29 it should be ok right now, bu in November 2021 it is required to use Api level 30 for existing app if you want to place them in google play store. And also in Api level 30 the flag requestLegacyExternalStorage stop working and you must use scope storage introduce in android 11 , right now this library is not ready for this change. Google play policy timeline https://developer.android.com/distribute/play-policies

saulowilson commented 3 years ago

Is there some deadline when the lib will be updated?

JDGarner commented 3 years ago

In case it helps anyone else, I switched to using https://github.com/rnmods/react-native-document-picker for now, it can be used as an Image/Video picker by passing in the correct types, e.g:

DocumentPicker.pickMultiple({
      type: [DocumentPicker.types.images, DocumentPicker.types.video],
    });

And doesn't require the use of requestLegacyExternalStorage to pick images from the gallery/other apps

thuynt99 commented 3 years ago

Have any plan add scope storage for android 11?

ghasemikasra39 commented 3 years ago

Any solution?

AathikaMSB-Rently commented 3 years ago

Any updates on this issue ?

mkamals commented 3 years ago

Waiting for solution to this issue.

thuynt99 commented 3 years ago

Waiting for solution to this issue.

KochMario commented 3 years ago

Any updates? πŸ€” @ivpusic I see that you started working on this on July 19th. Did you make any progress so far?

ivpusic commented 3 years ago

would be cool if somebody from the community has some time to finish support for android 11+

Caundy commented 3 years ago

With the deadline for existing apps near, are there any updates on the matter? :) @ivpusic could you describe what exactly is left to handle for the Android 11 support to be complete?

Pyroboomka commented 3 years ago

What actually needs to be done, what are possible issues? I tested this library on android 11 devices while targeting SDK 30. My use cases are pretty simple though: taking a picture, upload to backend; take/pick an image, crop it, upload to backend. Had no issues with current state of a library. Am I missing something?

fyfirman commented 3 years ago

I think so like @Pyroboomka . They said need to update the app after May 5th for new app. Last week i tried publish dummy app that use library requires storage access include react-native-image-crop-picker and Google approved my dummy app! You can check the app on this link, the app work perfectly fine. I didn't really understand the issue but i still worry about this issue and what happening on November.

mysport12 commented 3 years ago

I have been able to use the package as well without any issues. I am patching it though to update a number of the dependency versions and to resolve errors/warnings provided by Android Studio. Patch code in txt file below..

react-native-image-crop-picker+0.36.4.patch.txt

Pyroboomka commented 3 years ago

@fyfirman You'll need to bump targetSDK to 30 in November to continue updating your app. After doing it, if you currently have requestLegacyExternalStorage in manifest, it stops working on android 11 devices -> so we get some new issues. The ones I've personally found - you can no longer create files on sdcard freely (fixed by using cache directory instead); you can no longer freely rewrite files in downloads directory, if they are not created by your app (requires some renaming or just checking that files exists before writing them to disk). Nothing more came up.

5th May email was send to anyone, who had an requestLegacyExternalStorage in their manifest, made me google like a madman that day. Stumbled on https://www.reddit.com/r/androiddev/comments/mwaqn1/scoped_storage_recap/. Hope someone find it useful.

TL;DR: if stuff works as is, you don't need to worry.

KochMario commented 3 years ago

Since November is only a couple of days ahead I decided it's too risky to keep hoping for an update before then and therefore I've combined react-native-image-picker and react-native-image-crop-picker and implemented some sort of patchy solution with both of them.

Since react-native-image-picker is compatible with targetSdkVersion >= 30 (and therefore Android 11) I use that to access storage and capture images/videos with the camera. For additional cropping of the selected or captured photos I'm then additionally using react-native-image-crop-picker (since react-native-image-picker does not provide that functionality).

In my opinion, this works pretty decently (for the time being), since I've managed to cover the entire workflow I had before. The production release with that update was just accepted and published to Google Play Store.

If anyone is interested in the corresponding code, let me know and I'm happy to share it here.

kristijantomic commented 3 years ago

@KochMario i would like to see the code. Thx!

KochMario commented 3 years ago

@KochMario i would like to see the code. Thx!

import React from 'react';
import { ImagePickerResponse, launchCamera, launchImageLibrary } from 'react-native-image-picker';
import ImagePicker, { Image, Options as ImagePickerOptions } from 'react-native-image-crop-picker';
import {
  PermissionsAndroid,
  Platform,
  Pressable,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { getSystemVersion } from 'react-native-device-info';

const ImagesLimit = 5;
const config: ImagePickerOptionss = {
  // ... your options for react-native-image-crop-picker
  width: undefined,
  height: undefined,
  cropping: true,
  showsSelectedCount: true, // ios only
  compressImageQuality: 0.8, // value from 0 to 1 -> on iOS values larger than 0.8 basically have no effect
  compressImageMaxHeight: 2000,
  compressImageMaxWidth: 2000,
};
type Props = {
  width?: number;
  height?: number;
  onImageSelected: (selected) => void;
  multiple?: boolean | number;
  freeStyleCropEnabled?: boolean;
  useFrontCamera?: boolean; // Whether to default to the front/'selfie' camera when opened.
  compressImageMaxHeight?: number;
  compressImageMaxWidth?: number; 
};

const UploadPhotoModal = ({
  width,
  height,
  freeStyleCropEnabled = false,
  multiple = false,
  useFrontCamera = false,
  compressImageMaxHeight = 2000,
  compressImageMaxWidth = 2000,
  ...props
}: Props) => {
  const onError = (errorCode?, errorMessage?) => {
    console.log('πŸ“ΈπŸ“ΈπŸ“Έ Image Picker Error => ', errorCode, errorMessage);
    // ...
    // (if desired) - handle cropping error, e.g. show toast, modal, etc.
  };

  const cropAndReturnAssets = async assets => {
    const result: Image[] = [];
    if (assets && Array.isArray(assets)) {
      const openProps = {
        ...config,
        freeStyleCropEnabled,
        compressImageMaxHeight,
        compressImageMaxWidth,
      };

      const cropImage = async image => {
        try {
          return await ImagePicker.openCropper({
            path: image.uri as string,
            ...openProps,
          });
        } catch (e: any) {
          console.log('Cropper error', e.message, e.code);
          // if cropping was canceled return null and therefore "remove" this image from selection
          // else return the uncropped image -> might be because of image compatibility issues, e.g. .png sometimes doesn't work
          // with image cropper
          if (e.code === 'E_PICKER_CANCELLED') {
            return null;
          }
          return image;
        }
      };
      let uneditedImages = 0;

      try {
        for (const image of assets) {
          // for some reason the cropper can only handle jpg/jpeg images, therefore we push all other image types
          // without opening the cropper
          if (image.type === 'image/jpeg' || image.type === 'image/jpg') {
            const cropped = await cropImage(image);
            // only push if the image was edited and a valid result returned
            if (cropped) {
              result.push(cropped);
            }
          } else {
            uneditedImages += 1;
            result.push({ ...image, data: image.base64 });
          }
        }
      } catch (e) {
        console.log('Crop error: ', e);
        onError();
      }
      const croppedImages = result.map(image => 'data:image/jpeg;base64,' + image.data);
      if (croppedImages.length === 1) {
        props.onImageSelected(croppedImages[0]);
      } else {
        props.onImageSelected(croppedImages);
      }
      if (uneditedImages > 0) {
        // if desired - show information to the user the X of his selected images couldn't be processed and were therefore
        // added in their original state
        const message =
          uneditedImages === 1
            ? getTranslation('ImagePicker.ImageCouldNotBeEdited')
            : getTranslation('ImagePicker.ImagesCouldNotBeEdited', { amount: uneditedImages });
        // ...
        // logic for showing info message to the user
      }
    }
  };

  const handleResponse = async ({ assets, didCancel, errorCode, errorMessage }: ImagePickerResponse) => {
    if (errorCode || errorMessage) {
      onError(errorCode, errorMessage);
    } else if (didCancel) {
      // ...
      // logic for canceling
    } else {
      await cropAndReturnAssets(assets);
    }
  };

  const openImagePicker = async () => {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
    );

    const multipleValue =
      // ios > 14.0 supports specifying the max amount
      Platform.OS === 'ios' && getSystemVersion() > '14.0' && Number.isInteger(multiple)
        ? (multiple as number)
        : 0;
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      launchImageLibrary(
        {
          // ... your options
          mediaType: 'photo',
          quality: 0.9,
          selectionLimit: multiple ? multipleValue : 1,
          includeBase64: true,
        },
        handleResponse,
      );
    } else {
      // handle insufficient permissions, e.g. show modal or info message
    }
  };

  const openCamera = async () => {
    const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA);
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      launchCamera(
        {
          // ... your options
          mediaType: 'photo',
          quality: 0.9,
          cameraType: useFrontCamera ? 'front' : 'back',
          includeBase64: true,
          saveToPhotos: false,
        },
        handleResponse,
      );
    } else {
      // handle insufficient permissions, e.g. show modal or info message
    }
  };

  return (
    <View style={styles.container}>
      <Pressable onPress={openCamera}>
        <Text style={styles.text}>{getTranslation('UploadPhoto.TakePhoto')}</Text>
      </Pressable>
      <Pressable onPress={openImagePicker}>
        <Text style={styles.text}>{getTranslation('UploadPhoto.ChooseFromLibrary')}</Text>
      </Pressable>
    </View>
  );
};

export default UploadPhotoModal;

I've truncated some parts and reduced it somewhat so you get an idea without having too much of my custom logic in it. If using that code consider and keep in mind to customize it to your specific needs and purposes (i.e. I don't require video upload currently and for the time being I'm okay with only jpg's being compatible with cropping).

kristijantomic commented 3 years ago

@KochMario thank you very much. Cheers!

andresribeiro commented 3 years ago

@KochMario thanks

PaperMonster commented 2 years ago

I simply upgraded the library from 0.35.1 to 0.36.4 and removed requestLegacyExternalStorage from AndroidManifest.xml. Now I can pick and crop an image on Android 11 devices without any error. Is this good enough? I'm considering trying suggestions in this thread but unsure what exactly makes the latest version incomplete for Android 11.

marcshilling commented 2 years ago

@PaperMonster did you test on Android 10 after your changes?

PaperMonster commented 2 years ago

@PaperMonster did you test on Android 10 after your changes?

Yes I did. There was no problem.

Android 10 never had problem before and after the changes in target SDK and library version. On Android 11, cropping fails without requestLegacyExternalStorage=true. Upgrading to 0.36.4 fixed that.

SudoPlz commented 2 years ago

@PaperMonster does that mean the issue is fixed on 0.36.4 ? Can the maintainer of this library shed some light? Did someone work on fixing this? Thanks πŸ™

SudoPlz commented 2 years ago

~Ok to answer my question, this library is still using the deprecated WRITE_EXTERNAL_STORAGE permission and thus won't work on any app that uses Android API 30 and above (which since we're on November, is required to upload a binary to the store).~

https://github.com/ivpusic/react-native-image-crop-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java#L200

@ivpusic can you please confirm? ~Also are there any plans of fixing this properly (which as far as I know, would be to support the Data Storage api)~

UPDATE: I was wrong, this library no longer asks for that permission.

Thanks

Hesowcharov commented 2 years ago

@SudoPlz Hi! I can't absolutely agree with you because later piece of code https://github.com/ivpusic/react-native-image-crop-picker/blob/2b95b88f0b45025cccea0d39be582ff4cf311d1e/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java#L225-L233 conditionally removes WRITE_EXTERNAL_STORAGE for android 11 πŸ€” . Do you think it is still an issue if that code just refers to WRITE_EXTERNAL_STORAGE permission?

PaperMonster commented 2 years ago

@SudoPlz I released an app with version 0.36.4 + target API level 30 + WRITE_EXTERNAL_STORAGE in AndroidManifest last week on Google Play. It's been 4 days and there's no issue reported yet. At least I can confirm image picking, resizing, and cropping works as expected on Samsung Galaxy Note 20 + Android 11 in production.

SudoPlz commented 2 years ago

I can confirm that conditionally removing the WRITE_EXTERNAL_STORAGE permission stops our app from crashing too. Looks like that was all it took.

In our app things were a bit more complicated though, because an external dependency (instabug) specified a maxSdkVersion to the WRITE_EXTERNAL_STORAGE without us knowing about it (which resulted in weird behavior).

All good after all.

joy-betterhalf commented 2 years ago

@PaperMonster @SudoPlz

Can you guys please confirm if the play store has accepted your app after reviewing it? Because they have been rejecting our app and the reason for it is Scope storage access.

Even though our app doesn't ask for MANAGE_EXTERNAL_STORAGE permission they keep on rejecting our app saying that we are requesting MANAGE_EXTERNAL_STORAGE permission or not following best practicing.

Please check the below screenshot where clearly I can't see MANAGE_EXTERNAL_STORAGE permission as part of Manifest but WRITE_EXTERNAL_STORAGE is there which is conditionally handled by react-native-image-crop-picker and permission will not ask for Android 11. So, I don't understand the issue here and I don't understand why they are rejecting the app.

Screenshot 2021-12-23 at 1 19 20 AM

@ivpusic Please help

Thank you.

PaperMonster commented 2 years ago

@joy-betterhalf Yes, I can confirm mine. My app has WRITE_EXTERNAL_STORAGE but doesn't have MANAGE_EXTERNAL_STORAGE. In my case, WRITE_EXTERNAL_STORAGE is also needed for @react-native-community/cameraroll in Android < 10 but I had to write alternative native module for CameraRoll.save() for Android >= 10.

Perhaps you need to make sure requestLegacyExternalStorage=true is not in AndroidManifest.xml and check other dependencies if any of them uses forbidden API.

SudoPlz commented 2 years ago

Same here, I can confirm mine too, the app has WRITE_EXTERNAL_STORAGE but doesn't have MANAGE_EXTERNAL_STORAGE.

We don't use requestLegacyExternalStorage at all now, but I bet it wouldn't be a problem if we did. As @PaperMonster said make sure you check your merged manifest, and see what your other dependencies add to the final manifest. (We actually did have 1 dependency that messed with the WRITE_EXTERNAL_STORAGE dependency without our knowledge (instabug).

joy-betterhalf commented 2 years ago

@PaperMonster @SudoPlz

Thank you guys for your quick response. I really appreciate your help. This information will help me solve my issue and hopefully Play store will accept our app. 🀞

retyui commented 2 years ago

I have been able to use the package as well without any issues. I am patching it though to update a number of the dependency versions and to resolve errors/warnings provided by Android Studio. Patch code in txt file below..

react-native-image-crop-picker+0.36.4.patch.txt

Thanks @mysport12, your fix is working for me!

patch-package + react-native-image-crop-picker+0.36.4.patch.txt = ❀️

RodolfoGS commented 1 year ago

Any update about this, it's still ocurring with 0.40.0 + react-native 0.72.4 Error code: E_NO_LIBRARY_PERMISSION

RodolfoGS commented 1 year ago

I created a PR that fix it https://github.com/ivpusic/react-native-image-crop-picker/pull/1973