188

Working on android Java, recently updated SDK to API level 29 now there is a warning shown which states that

Environment.getExternalStorageDirectory() is deprecated in API level 29

My code is

private void saveImage() {

if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

    final String folderPath = Environment.getExternalStorageDirectory() + "/PhotoEditors";
    File folder = new File(folderPath);
    if (!folder.exists()) {
        File wallpaperDirectory = new File(folderPath);
        wallpaperDirectory.mkdirs();
    }


    showLoading("Saving...");
    final String filepath=folderPath
                + File.separator + ""
                + System.currentTimeMillis() + ".png";
    File file = new File(filepath);

    try {
        file.createNewFile();
        SaveSettings saveSettings = new SaveSettings.Builder()
                .setClearViewsEnabled(true)
                .setTransparencyEnabled(true)
                .build();
        if(isStoragePermissionGranted() ) {
            mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() {
            @Override
            public void onSuccess(@NonNull String imagePath) {
                hideLoading();
                showSnackbar("Image Saved Successfully");
                mPhotoEditorView.getSource().setImageURI(Uri.fromFile(new File(imagePath)));
                sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(filepath))));
                Intent intent = new Intent(EditImageActivity.this, StartActivity.class);
                startActivity(intent);
                finish();

            } 

            @Override
            public void onFailure(@NonNull Exception exception) {
                hideLoading();
                showSnackbar("Failed to save Image");
            }
       });
   }

What will be the alternative for this?

Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
Noaman Akram
  • 3,680
  • 4
  • 20
  • 37

13 Answers13

185

Use getExternalFilesDir(), getExternalCacheDir(), or getExternalMediaDirs() (methods on Context) instead of Environment.getExternalStorageDirectory().

Or, modify mPhotoEditor to be able to work with a Uri, then:

  • Use ACTION_CREATE_DOCUMENT to get a Uri to a location of the user's choosing, or

  • Use MediaStore, ContentResolver, and insert() to get a Uri for a particular type of media (e.g., an image) — see this sample app that demonstrates doing this for downloading MP4 videos from a Web site

Also, note that your Uri.fromFile with ACTION_MEDIA_SCANNER_SCAN_FILE should be crashing on Android 7.0+ with a FileUriExposedException. On Android Q, only the MediaStore/insert() option will get your content indexed by the MediaStore quickly.

Note that you can opt out of these "scoped storage" changes on Android 10 and 11, if your targetSdkVersion is below 30, using android:requestLegacyExternalStorage="true" in the <application> element of the manifest. This is not a long-term solution, as your targetSdkVersion will need to be 30 or higher sometime in 2021 if you are distributing your app through the Play Store (and perhaps elsewhere).

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 35
    Dear @CommonsWare, should we ever meet in real live, please remind me that I owe you a beer for your blog post. I spent a whole afternoon finding out why a certain external library stopped working, as soon as I raised the targetSdk level to 29. Now I know why ... – Nantoka Sep 23 '19 at 21:04
  • @Commonsware can you provide example or appropriate code with usage of your answer? – Quick learner Dec 03 '19 at 10:53
  • 2
    using getExternalFilesDir() and getExternalCacheDir() can work with api 29 but when app unistalled all data will be removed using this methods ... If you use this attribute inside application tag "android:requestLegacyExternalStorage="true" " can work also for api 29 . When app unistalled file name exits but data removed – Ucdemir Apr 21 '20 at 08:55
  • Hi , how can we use `getExternalFilesDir()` with custom path for save image , i want to use this for prevent show images into Gallery? In default it save to `Andorid/data/packagename/...` – milad salimi Apr 22 '20 at 10:55
  • Hi please check my question. – milad salimi Apr 22 '20 at 18:38
  • 1
    @miladsalimi: Sorry, but I do not understand your question. I suggest that you ask a separate Stack Overflow question where you explain in detail what your concern is. – CommonsWare Apr 22 '20 at 18:48
  • 10
    There is no `getExternalMediaDir` in `Context`, see it yourself: https://developer.android.com/reference/android/content/Context And `getExternalMediaDirs` is deprecated, also look: https://developer.android.com/reference/android/content/Context#getExternalMediaDirs() Both `getExternalFilesDir()` and `getExternalCacheDir()` are deleted upon application uninstall (see the same URL). So, none of above can substitute `Environment.getExternalStorageDirectory()`. – Dims Jul 08 '20 at 13:18
  • @Dims: My apologies for missing the `s` in `getExternalMediaDirs()`. `getExternalMediaDirs()` is deprecated in Android 11. "So, none of above can substitute Environment.getExternalStorageDirectory()" -- for your use case, that may be true. Your use case is not the same as everybody else's use case. And, for your use case, eventually you will need to migrate to `ACTION_CREATE_DOCUMENT` or using `MediaStore`. – CommonsWare Jul 08 '20 at 13:46
  • 2
    It's not about mine usecase, but topicstarter's usecase above. – Dims Jul 09 '20 at 14:13
  • What i must be use to save image file on external memory? – luke cross Jul 27 '20 at 19:46
  • getExternalMediaDirs() is also deprecated – Ali Azaz Alam Oct 06 '20 at 06:24
  • 1
    What happens if ContentResolver.insert() is successful, but saving the file failed, doesn't that pollute the media database? – ssynhtn Nov 12 '20 at 06:57
  • @ssynhtn: If your concern is downloading straight from the Internet to the `MediaStore`-supplied location, that is indeed a possible risk. To mitigate that a bit, you could download to a file (e.g., in `getCacheDir()`), and only then `insert()` into the `MediaStore` and copy over the content. This is still not perfect, as you might run out of disk space making the separate copy, but it is better than relying on the network. Alternatively, download to the `MediaStore`-supplied location, and if you detect the download failed, `delete()` that `MediaStore` entry. – CommonsWare Nov 12 '20 at 12:15
  • @CommonsWare yes that is what i referred to. thanks for the clarification. but i think the new way of doing things makes little sense. guess I'll keep using the deprecated method until it breaks – ssynhtn Nov 13 '20 at 13:08
  • Ucdemir rightly pointed out that upon uninstalling the app, the directory gets DELETED as well. If you need to give the user the possibility of opting to keep user data add this line: android:hasFragileUserData="true" in the android manifest file under "application" see example: – Marius Rusu Jan 29 '21 at 21:12
  • i tried this code, its not working for me in Samsung a70 android 10. directory is not created when using this code. getExternalStorageDirectory() is working but it is deprecated..any solution? – athul Feb 23 '21 at 12:39
  • 1
    @athul: I do not know what "this code" refers to. I recommend that you ask a separate Stack Overflow question, where you provide a [mcve] showing what you tried and explaining in detail where your problem arose. – CommonsWare Feb 23 '21 at 12:46
  • Imagine using `DownloadManager` and `FileProvider` to display the downloaded file. `DownloadManager` won't save into private directory and `FileProvider` won't allow displaying files in a public directory with `ACTION_VIEW` action. Android public APIs are messed up for the sake of `security` which they should have thought about 10 years ago. Wonder if they even care about other public APIs that their new measures render invalid – Farid Feb 26 '21 at 17:37
  • `getExternalMediaDirs()` is deprecated in **`Context.class`** but not in `public class ContextWrapper extends Context` so how to store file inside `Android/media/com.yourpackage` like **whatsapp** did ? They moved whatsapp folder inside that. – Bhavin Patel Jul 06 '21 at 11:01
49

Get the destPath with the new API call:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
Russell A
  • 615
  • 4
  • 3
  • 3
    We can use this method: https://developer.android.com/training/data-storage/app-specific#external-select-location ? What do you think about it ? :) – aeroxr1 Jan 07 '20 at 08:54
  • 3
    Can still get the environment variable getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) replaces Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) – Rowan Berry Mar 06 '20 at 03:04
  • Updated the deprecated method to this FIXED "Permission denied" error from createNewFile for external storage targeting 29! Thank you! – coolcool1994 Aug 22 '20 at 19:13
  • 9
    This is not the public directory – Irfan Ul Haq Oct 21 '20 at 08:45
  • i tried this code, its not working for me in Samsung a70 android 10. directory is not created when using this code. getExternalStorageDirectory() is working but it is deprecated..any solution? – athul Feb 23 '21 at 12:39
34

For Android Q, you can add android:requestLegacyExternalStorage="true" to your element in the manifest. This opts you into the legacy storage model, and your existing external storage code will work.

<manifest ... >
<!-- This attribute is "false" by default on apps targeting
     Android 10 or higher. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Technically, you only need this once you update your targetSdkVersion to 29. Apps with lower targetSdkVersion values default to opting into legacy storage and would need android:requestLegacyExternalStorage="false" to opt out.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
mahmoud mansour
  • 537
  • 5
  • 3
  • 1
    This post has saved my search for a solution of storing data on external storage in the latest version of Android Studio with an earlier developed code - which lasted 8 hours. Thank you. – Sriram Nadiminti Jul 12 '20 at 17:09
  • 2
    It will work till API 29. "Caution: After you update your app to target Android 11 (API level 30), the system ignores the requestLegacyExternalStorage attribute when your app is running on Android 11 devices, so your app must be ready to support scoped storage and to migrate app data for users on those devices." https://developer.android.com/training/data-storage/use-cases – avisper Sep 14 '20 at 13:32
  • 1
    Not recommended – Cícero Moura Apr 21 '21 at 00:36
10

Please use getExternalFilesDir(), getExternalCacheDir() instead of Environment.getExternalStorageDirectory() when you creating file in Android 10.

See below line:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Hardik Hirpara
  • 2,594
  • 20
  • 34
  • 1
    Hi , how can we use `getExternalFilesDir()` with custom path for save image , i want to use this for prevent show images into Gallery? In default it save to `Andorid/data/packagename/...` – milad salimi Apr 22 '20 at 10:58
  • You will need to save the file to app directory and then insert this using Media uri – Ajith M A May 07 '20 at 18:48
  • 24
    wrong answer ..actual question how to get /storage/emulated/0/MyFolder/ but your anser /data/user/0/com.example.package/files/MyFolder – Tarif Chakder Aug 27 '20 at 09:58
  • 1
    i tried this code, its not working for me in Samsung a70 android 10. directory is not created when using this code. getExternalStorageDirectory() is working but it is deprecated..any solution? – athul Feb 23 '21 at 12:38
9

This is a small example how to get URI for a file if you want to take photo using default camera and store it in a DCIM folder (DCIM/app_name/filename.jpg):

Open camera (remember about CAMERA permission):

private var photoURI: Uri? = null

private fun openCamera() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        photoURI = getPhotoFileUri()
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
        takePictureIntent.resolveActivity(requireActivity().packageManager)?.also {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
        }
    }
}

And get URI:

private fun getPhotoFileUri(): Uri {
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
    val fileName = "IMG_${timeStamp}.jpg"

    var uri: Uri? = null
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val resolver = requireContext().contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/app_name/")
        }

        uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    }

    return uri ?: getUriForPreQ(fileName)
}

private fun getUriForPreQ(fileName: String): Uri {
    val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
    val photoFile = File(dir, "/app_name/$fileName")
    if (photoFile.parentFile?.exists() == false) photoFile.parentFile?.mkdir()
    return FileProvider.getUriForFile(
        requireContext(),
        "ru.app_name.fileprovider",
        photoFile
    )
}

Don't forget about WRITE_EXTERNAL_STORAGE permission for pre Q and add a FileProvider to AndroidManifest.xml.

And get a result:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
        REQUEST_IMAGE_CAPTURE -> {
            photoURI?.let {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    val thumbnail: Bitmap =
                        requireContext().contentResolver.loadThumbnail(
                            it, Size(640, 480), null
                        )
                } else {
                    // pre Q actions
                }
            }
        }
    }
}
bitvale
  • 1,959
  • 1
  • 20
  • 27
6

This worked for me

Add this line in application tag of manifest file

android:requestLegacyExternalStorage="true"

Example

 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:requestLegacyExternalStorage="true"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

 </application>

Target SDK is 29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
Quick learner
  • 10,632
  • 4
  • 45
  • 55
6

To get internal storage directory without hard coding,

Permissions (For all Android versions). Don't forget to get permissions from the user.

Request all files access

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />

Legacy permission for Android 10 (add this to AndroidManifest.xml > application tag).

android:requestLegacyExternalStorage="true"

Get internal storage directory path:

public static String getInternalStorageDirectoryPath(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        return storageManager.getPrimaryStorageVolume().getDirectory().getAbsolutePath();
    } else {
        return Environment.getExternalStorageDirectory().getAbsolutePath();
    }
}
Atakan Yildirim
  • 684
  • 11
  • 22
3

This worked

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    contentResolver?.also { resolver ->
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "Image_"+".jpg")
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator+ "TestFolder")
        }
        val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
            fos = imageUri?.let { resolver.openOutputStream(it) }
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos)
            Objects.requireNonNull(fos)
    }
}
anatoli
  • 1,663
  • 1
  • 17
  • 43
3

Recently I also face similar kind of issues, Due to my code was large and i don't want to add new functionality in existing code so i just simply change path..

Environment.getExternalStorageDirectory() Replace with

context.getExternalFilesDir(null).getAbsolutePath()

Code

private void saveImage() {

if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

final String folderPath = this.getExternalFilesDir(null).getAbsolutePath() + "/PhotoEditors";
File folder = new File(folderPath);
if (!folder.exists()) {
    File wallpaperDirectory = new File(folderPath);
    wallpaperDirectory.mkdirs();
}


showLoading("Saving...");
final String filepath=folderPath
            + File.separator + ""
            + System.currentTimeMillis() + ".png";
File file = new File(filepath);

try {
    file.createNewFile();
    SaveSettings saveSettings = new SaveSettings.Builder()
            .setClearViewsEnabled(true)
            .setTransparencyEnabled(true)
            .build();
    if(isStoragePermissionGranted() ) {
        mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() {
        @Override
        public void onSuccess(@NonNull String imagePath) {
            hideLoading();
            showSnackbar("Image Saved Successfully");
            mPhotoEditorView.getSource().setImageURI(Uri.fromFile(new File(imagePath)));
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(filepath))));
            Intent intent = new Intent(EditImageActivity.this, StartActivity.class);
            startActivity(intent);
            finish();

        } 

        @Override
        public void onFailure(@NonNull Exception exception) {
            hideLoading();
            showSnackbar("Failed to save Image");
        }
   });

}

Piyush
  • 1,156
  • 12
  • 20
2

You could make use of StorageManager & StorageVolume classes

StorageVolume.getPrimaryStorageVolume(): This volume is the same storage device returned by Environment#getExternalStorageDirectory() and Context#getExternalFilesDir(String).

    public String myGetExternalStorageDir() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
            return getPrimaryStorageVolumeForAndroid11AndAbove();
        else
            return getPrimaryStorageVolumeBeforeAndroid11();
    }

    @TargetApi(Build.VERSION_CODES.R)
    private String getPrimaryStorageVolumeForAndroid11AndAbove() {
        StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
        StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
        return mySV.getDirectory().getPath();
    }

    private String getPrimaryStorageVolumeBeforeAndroid11() {
        String volumeRootPath = "";
        StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
        StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
        Class<?> storageVolumeClazz = null;

        try {
            storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
            Method getPath = storageVolumeClazz.getMethod("getPath");
            volumeRootPath = (String) getPath.invoke(mySV);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return volumeRootPath;
    }
usilo
  • 305
  • 2
  • 10
  • Hey @usilo, thanks for your answer first, one thing is wrong with this code, when I use `getPrimaryStorageVolumeBeforeAndroid11` method's code, it won't support all lower versions because `myStorageManager.getPrimaryStorageVolume()` can be accessed from android N only. Still below than lower N version is in question mark with your answer. Thanks – SaravanaRaja Sep 23 '21 at 17:59
2
private fun saveImage(bitmap: Bitmap, name: String) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val resolver = contentResolver
        val contentValues = ContentValues()
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + "YOUR_FOLDER")
        val imageUri =
            resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        val fos = resolver.openOutputStream(imageUri!!)
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
        fos!!.flush()
        fos.close()

        toast("Saved to gallery")

    } else {
        if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            val imagesDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_DCIM
            ).toString() + File.separator + "YOUR_FOLDER"
            if (!file.exists()) {
                file.mkdir()
            }
            val image = File(imagesDir, "$name.png")

            val fos = FileOutputStream(image)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
            fos.flush()
            fos.close()

        } else {
            // ask for permission
        }
    }
}
mad_lad
  • 654
  • 3
  • 8
  • 20
0
  var tempDir: File = inContext.getExternalFilesDir("/")!!
        tempDir = File(tempDir.getAbsolutePath().toString() + "/.IncidentImages/")
        tempDir.mkdir()
-1

If you work with XAMARIN and are confused by all this different answers (like me) just follow this example:

var picture = new Java.IO.File(Environment.DirectoryPictures, "fileName");

Took me some time to figure this out.

user3772108
  • 854
  • 2
  • 14
  • 32