By now probably all of you had to hear about the dreaded changes to file storage on Android 11.
So what exactly changed?
According to Google apps now “have access only to the app-specific directory on external storage, as well as specific types of media that the app has created.”

In other words if your application freely allows the users to save files in shared storage, I hate to be the one that tells you this - you may have a problem sir.
This is the case especially if you’ve been using Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_*) in your code.
The getExternalStoragePublicDirectory no longer returns a path to a publicly accessible folder so if you save a file in that location the user simply won’t see it.

But worry not, there are some alternatives which I will talk about in this article.

There are two recommended approaches when it comes to shared storage file saving on Android 11

If you’re already using them you should be fine and you can simply skip reading further.

MediaStore API

Let’s assume you want to save a bitmap as an image named screenshot.jpg inside the pictures directory.
In order to do that using MediaStore API you can use the below snippet.

private fun saveImageToStorage(
    bitmap: Bitmap,
    filename: String = "screenshot.jpg",
    mimeType: String =  "image/jpeg",
    directory: String = Environment.DIRECTORY_PICTURES,
    mediaContentUri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
) {
    val imageOutStream: OutputStream
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val values = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, filename)
            put(MediaStore.Images.Media.MIME_TYPE, mimeType)
            put(MediaStore.Images.Media.RELATIVE_PATH, directory)
        } {
            val uri =
                contentResolver.insert(mediaContentUri, values)
                    ?: return
            imageOutStream = openOutputStream(uri) ?: return
    } else {
        val imagePath = Environment.getExternalStoragePublicDirectory(directory).absolutePath
        val image = File(imagePath, filename)
        imageOutStream = FileOutputStream(image)

    imageOutStream.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }

In a case when saving other type of media file you might need to modify filename, mimeType and mediaContentUri accordingly.
It is worth noting that the function will work both on the scoped as well as legacy storage systems.

Storage Access Framework (SAF)

As mentioned above you should generally use SAF for documents and other types of files.
The following snippet can be used to launch system file picker and allow the user to pick a directory where the file will be stored.

const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    startActivityForResult(intent, CREATE_FILE)

It’s not over yet!
After the user has picked a directory we still need to handle the result Uri in on onActivityResult method.

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // save your data using the `uri`

Yes that’s it!
Essentially use the above two approaches whenever you want to save files in directories that you want the user to have access to.

In this post we have learned how to save files in shared storage on Android 11.