21

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.

Robert
  • 245
  • 1
  • 2
  • 5

4 Answers4

21

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.

Noor Hossain
  • 1,248
  • 1
  • 14
  • 21
  • `dir.mkdirs();` mkdirs() does not throw exceptions but returns true or false. If it returns false you should let your commonDocumentDirPath function return a null. And the caller should check for null. – blackapps Feb 22 '21 at 15:43
  • ??? You removed the whole mkdirs() call. Please add it again. You cannot do without. – blackapps Feb 22 '21 at 15:55
  • while saving file with getParentDirs I have saved it. – Noor Hossain Feb 22 '21 at 16:00
  • 11
    getExternalStoragePublicDirectory & getExternalStorageDirectory both are deprecated... – Bhavesh Moradiya Apr 05 '21 at 11:23
  • 2
    @BhaveshMoradiya, yes, But now in Android 11 for the above approaches works well for all versions correspondingly. Thanks commonsware hints. – Noor Hossain Apr 17 '21 at 16:00
  • @NoorHossain can you share the full code snippet about the camera image store? I want to capture a profile picture and store it. I tried other methods available (using manage storage permission) but while uploading on playstore it blocked my app. Can you please help me – mr.volatile Jun 14 '21 at 10:16
  • @VaibhavA, sorry brother I don't know about blocked. But you can test the above code for image folder or picture folder. And with runtime permission. I do not know if this solved your problem. You can ask your question in stack overflow with your codes. – Noor Hossain Jun 15 '21 at 16:57
  • @NoorHossain I've asked a question here, please answer if you know.. https://stackoverflow.com/q/67984238/7850506 – mr.volatile Jun 16 '21 at 05:58
  • It's working fine, Can you suggest to read status as well from Whatsapp? I am trying to read status from this location "Android/media/com.whatsapp/WhatsApp/Media/.Statuses" but not able to get it. – Md Mohsin Aug 06 '21 at 11:22
  • getExternalStoragePublicDirectory is deprecated now.. – Aditya Patil Sep 14 '21 at 09:28
  • @AdityaPatil, You do not know, getExternalStoragePublicDirectory came again! – Noor Hossain Feb 14 '22 at 23:04
7

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.

blackapps
  • 6,227
  • 2
  • 6
  • 19
5

**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.
                        }
                    }
                });
Davinder Kamboj
  • 311
  • 3
  • 7
3

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" />
Mark Nashat
  • 380
  • 4
  • 6
  • Hi @Mark Nashat how to do for android version less than Q. By this I mean how to handle both situation? – Kundan Jha Oct 24 '21 at 11:36
  • by this the code File filePath = Environment.getExternalStorageDirectory(); File dir = new File(filePath.getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/"); dir.mkdir(); File file1 = new File(dir, System.currentTimeMillis() + ".jpg"); – Mark Nashat Oct 25 '21 at 02:01
  • try { outputStream = new FileOutputStream(file1); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show(); } catch (FileNotFoundException e) { Toast.makeText(context, "Image Not Saved: \n " + e, Toast.LENGTH_SHORT).show(); e.printStackTrace(); } try { outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace() } } – Mark Nashat Oct 25 '21 at 02:04
  • Thank you so much @Mark Nashat. Also, one last thing is how to know if a file exists in external storage in android 10+. Actually, I want to create the file if it does not exist else overwrite it. (Like you suggested answer above) – Kundan Jha Oct 25 '21 at 16:29