Environment.getExternalStorageDirectory() deprecated in API level 29 java
AndroidDeprecatedAndroid Api-LevelsAndroid Problem Overview
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?
Android Solutions
Solution 1 - Android
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 aUri
to a location of the user's choosing, or -
Use
MediaStore
,ContentResolver
, andinsert()
to get aUri
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).
Solution 2 - Android
Get the destPath
with the new API call:
String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Solution 3 - Android
For Android Q, you can add android:requestLegacyExternalStorage="true"
to your
<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.
Solution 4 - Android
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")
Solution 5 - Android
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
}
}
}
}
}
Solution 6 - Android
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"
}
Solution 7 - Android
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)
}
}
Solution 8 - Android
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;
}
Solution 9 - Android
To get internal storage directory without hard coding,
Permissions (For all Android versions)
<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" />
In Android 10 and below we can use our old permissions, but in Android 10 we have to use the requestLegacyExternalStorage in our application tag (AndroidManifest.xml > application tag).
android:requestLegacyExternalStorage="true"
Get internal storage directory path:
@Nullable
public static String getInternalStorageDirectoryPath(Context context) {
String storageDirectoryPath;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
if(storageManager == null) {
storageDirectoryPath = null; //you can replace it with the Environment.getExternalStorageDirectory().getAbsolutePath()
} else {
storageDirectoryPath = storageManager.getPrimaryStorageVolume().getDirectory().getAbsolutePath();
}
} else {
storageDirectoryPath = Environment.getExternalStorageDirectory().getAbsolutePath();
}
return storageDirectoryPath;
}
Solution 10 - Android
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
}
}
}
Solution 11 - Android
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");
}
});
}
Solution 12 - Android
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.
Solution 13 - Android
var tempDir: File = inContext.getExternalFilesDir("/")!!
tempDir = File(tempDir.getAbsolutePath().toString() + "/.IncidentImages/")
tempDir.mkdir()