Saving files in Android 11 to external storage(SDK 30)

Viewed 39063

I am writing a new Application on Android 11 (SDK Version 30) and I simply cannot find an example on how to save a file to the external storage.

I read their documentation and now know that they basicly ignore Manifest Permissions (READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE). They also ignore the android:requestLegacyExternalStorage="true" in the manifest.xml application tag.

In their documentation https://developer.android.com/about/versions/11/privacy/storage they write you need to enable the DEFAULT_SCOPED_STORAGE and FORCE_ENABLE_SCOPED_STORAGE flags to enable scoped storage in your app.

Where do I have to enable those? And when I've done that how and when do I get the actual permission to write to the external storage? Can someone provide working code?
I want to save .gif, .png and .mp3 files. So I don't want to write to the gallery.

Thanks in advance.

4 Answers

Corresponding To All Api, included Api 30, Android 11 :

public static File commonDocumentDirPath(String FolderName)
{
    File dir = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
    {
        dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + FolderName);
    }
    else
    {
        dir = new File(Environment.getExternalStorageDirectory() + "/" + FolderName);
    }

    // Make sure the path directory exists.
    if (!dir.exists())
    {
        // Make it, if it doesn't exit
        boolean success = dir.mkdirs();
        if (!success)
        {
            dir = null;
        }
    }
    return dir;
}

Now, use this commonDocumentDirPath for saving file.

A side note from comments, getExternalStoragePublicDirectory with certain scopes are now working with Api 30, Android 11. Cheers! Thanks to CommonsWare hints.

You can save files to the public directories on external storage.

Like Documents, Download, DCIM, Pictures and so on.

In the usual way like before version 10.

**Simplest Answer and Tested ( Java ) **

private void createFile(String title) {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("text/html");
        intent.putExtra(Intent.EXTRA_TITLE, title);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("/Documents"));
    }
    createInvoiceActivityResultLauncher.launch(intent);
}

private void createInvoice(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getContentResolver().
                openFileDescriptor(uri, "w");
        if (pfd != null) {
            FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
            fileOutputStream.write(invoice_html.getBytes());
            fileOutputStream.close();
            pfd.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/////////////////////////////////////////////////////
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed


    String invoice_html;
    ActivityResultLauncher<Intent> createInvoiceActivityResultLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

       invoice_html = "<h1>Just for testing received...</h1>";
       createInvoiceActivityResultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        // There are no request codes
                        Uri uri = null;
                        if (result.getData() != null) {
                            uri = result.getData().getData();
                            createInvoice(uri);
                            // Perform operations on the document using its URI.
                        }
                    }
                });

I'm using this method and it really worked for me I hope I can help you. Feel free to ask me if something is not clear to you

   Bitmap imageBitmap;

   OutputStream outputStream ;
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
            {
                ContentResolver resolver = context.getContentResolver();
                ContentValues contentValues = new ContentValues();
                contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,"Image_"+".jpg");
                contentValues.put(MediaStore.MediaColumns.MIME_TYPE,"image/jpeg");
                contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_PICTURES + File.separator+"TestFolder");
                Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
                try {
                    outputStream =  resolver.openOutputStream(Objects.requireNonNull(imageUri) );
                    imageBitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
                    Objects.requireNonNull(outputStream);
                    Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show();
                    
                } catch (Exception e) {
                    Toast.makeText(context, "Image Not Not  Saved: \n "+e, Toast.LENGTH_SHORT).show();
    
                    e.printStackTrace();
                }
    
            }

manifest file (Add Permission)

<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
Related