getExternalStoragePublicDirectory deprecated in Android Q

The question:

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:

  1. Context#getExternalFilesDir(String)
  2. MediaStore

Option 1 is out of the questions as it would mean that the photos get deleted if the app gets uninstalled.

Option 3 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 2, 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.

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

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.

Method 2

@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:

Also, where I have the variable name, you replace it with the name of the file in your case.

Method 3

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" ... >

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");

Method 4

import android.content.ContentValues
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val editText: EditText = findViewById(
        val write: Button = findViewById(
        val read: Button = findViewById(
        val textView: TextView = findViewById(

        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 {

        read.setOnClickListener {
            if (uri != null) {
                resolver.openInputStream(uri).use {
                    val data = ByteArray(50)
                    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.

Method 5

For Xamarin.Android the following code from a published project could help:

    Java.IO.File jFolder;

    if ((int)Android.OS.Build.VERSION.SdkInt >= 29)
        jFolder = new Java.IO.File(Android.App.Application.Context.GetExternalFilesDir(Environment.DirectoryDcim), "Camera");
        jFolder = new Java.IO.File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDcim), "Camera");

    if (!jFolder.Exists())

    var filename = GenerateJpgFileName();
    var jFile = new Java.IO.File(jFolder, filename);
    var fullFilename = jFile.AbsoluteFile.ToString();

    using (var output = new System.IO.FileStream(fullFilename, System.IO.FileMode.Create))
        outputBitmap.Compress(Bitmap.CompressFormat.Jpeg, 90, output);

Not using permissions in Android, it’s done in from the shared project using Xamarin.Essentials.

Method 6

Generally I used this way :

     var data: File =Environment.getExternalStoragePublicDirectory
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        data = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!

Method 7

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,

Details are given in the following link:

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment