Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
As
getExternalStoragePublicDirectory
has been deprecated in
Android Q
, and the recommendation is to use other means. then how can we specify that we want to store the generated photos from a camera app into the
DCIM
folder, or a custom sub-folder within the
DCIM
?
The documentation states that the next 3 options are the new preferred alternatives:
Context#getExternalFilesDir(String)
Intent#ACTION_OPEN_DOCUMENT
MediaStore
Option 1 is out of the questions as it would mean that the photos get deleted if the app gets uninstalled.
Option 2 is also not a choice, as it would require the user to pick the location through the SAF file explorer.
We are left with option 3, the MediaStore; but at the time of this question there is no documentation on how to use it as a replacement for getExternalStoragePublicDirectory in Android Q.
Based on the docs, use
DCIM/...
for
the
RELATIVE_PATH
, where
...
is whatever your custom subdirectory would be. So, you would wind up with something like this:
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "CuteKitten001")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/PerracoLabs")
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
resolver.openOutputStream(uri).use {
// TODO something with the stream
Note that since RELATIVE_PATH
is new to API Level 29, you would need to use this approach on newer devices and use getExternalStoragePublicDirectory()
on older ones.
–
–
–
–
@CommonsWare answer is amazing. But for those who want it in Java, you need to try this:
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
As per the suggestion of @SamChen the code should look like this for text files:
Uri uri = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues);
Because we wouldn't want txt files lingering in the Images folder.
So, the place where I have mimeType
, you enter the mime type you want. For example if you wanted txt
(@Panache) you should replace mimeType
with this string: "text/plain"
. Here is a list of mime types: https://www.freeformatter.com/mime-types-list.html
Also, where I have the variable name
, you replace it with the name of the file in your case.
–
–
–
–
Apps targeting Android Q - API 29+ disabled storage access by default due to security issues. If you want to enable it to add the following attribute in the AndroidManifest.xml:
<manifest ... >
<!-- This attribute is "false" by default for Android Q or higher -->
<application android:requestLegacyExternalStorage="true" ... >
</application>
</manifest>
then you have to use getExternalStorageDirectory()
instead of getExternalStoragePublicDirectory()
.
Example: If you want to create a directory in the internal storage if not exists.
File mediaStorageDir = new File(Environment.getExternalStorageDirectory() + "/SampleFolder");
// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("error", "failed to create directory");
–
–
–
–
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
class MyActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mine)
val editText: EditText = findViewById(R.id.edt)
val write: Button = findViewById(R.id.Output)
val read: Button = findViewById(R.id.Input)
val textView: TextView = findViewById(R.id.textView)
val resolver = this.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "myDoc1")
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Documents")
val uri: Uri? = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
Log.d("Uri", "$uri")
write.setOnClickListener {
val edt : String = editText.text.toString()
if (uri != null) {
resolver.openOutputStream(uri).use {
it?.write("$edt".toByteArray())
it?.close()
read.setOnClickListener {
if (uri != null) {
resolver.openInputStream(uri).use {
val data = ByteArray(50)
it?.read(data)
textView.text = String(data)
Here, I am storing a text file in phone's Document folder by writing text into edit text and by clicking button 'Write' it will save the file with the text written.
On clicking button 'Read' it will bring the text from that file and then display it in the text view.
It will not run on devices that are below android Q or android 10 as RELATIVE_PATH can only be used in these versions.
–
If you want to save your file in a app specific external storage, yes you can use context.getExternalFilesDir(). Many answers point out that.
However, this is not the answer of this question because getExternalFilesDir() is app specific external storage, getExternalStoragePublicDirectory() is shared storage.
For example, you want to save a downloaded pdf file to "Shared" Download directory. How do you do that ? For api 29 and above, you can do that without no permission.
For api 28 and below, you need getExternalStoragePublicDirectory() method but it is deprecated. What if you don't want to use that deprecated method? Then you can use SAF file explorer(Intent#ACTION_OPEN_DOCUMENT). As said in the question, this requires the user to pick the location manually.
This is what Google wants exactly. To improve user privacy, direct access to shared/external storage devices is deprecated.
When an app targets Build.VERSION_CODES.Q, the path returned from this
method is no longer directly accessible to apps. Apps can continue to
access content stored on shared/external storage by migrating to
alternatives such as Context#getExternalFilesDir(String), MediaStore,
or Intent#ACTION_OPEN_DOCUMENT.
Details are given in the following link:
https://developer.android.com/reference/android/os/Environment#getExternalStorageDirectory()
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.