Alternative/Replacement of getExternalStorageDirectory for android Q+ versions.

What was valid before Android Q for this purpose? …

Earlier, we used to access phones external storage’s directories via using a public class Environment which provides us with some methods like getExternalStorageDirectory/ getExternalStoragePublicDirectories.

We would just have to create a file and by calling Environment.getExternalStorageDirectory(Path, Filename), our file would get created on the specified path and then we can perform Read and Write operations on our file.

I had mentioned an example for creating a text file in phone’s document folder…

class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mine)
val textView: TextView = findViewById(R.id.textView)
val myText: String = textView.text.toString()
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "abc.txt")

val fileName = "abc.txt"
val folder = Environment.getExternalStorageDirectory()
val myFile = File(folder, fileName)
val fStream = FileInputStream(myFile)
val sBuffer = StringBuffer()
var i: Int
while (fStream.read().also { i = it } != -1) {
sBuffer.append(i.toChar())
}
fStream.close()
val details = sBuffer.toString()
textView.text = details
writeTextData(myFile,details)

if (!myFile.exists()) {
try {
myFile.createNewFile()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

private fun writeTextData(file: File, data: String) {
val fileOutputStream: FileOutputStream? = null
try {
fileOutputStream?.write(data.toByteArray())

Toast.makeText(this, "Done" + file.absolutePath, Toast.LENGTH_SHORT).show()
} catch (e: java.lang.Exception) {
e.printStackTrace()
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
}

What & Why has been changed?

Now, from Android Q or Android 10 onwards, getExternalStorageDirectory method has been deprecated.
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.

How to work now for same type of requirement?

1. We can Use getExternalFilesDir(), getExternalCacheDir(), or getExternalMediaDirs() (methods on Context) instead of Environment.getExternalStorageDirectory() for fulfilling not same but little bit similar requirements.
From these methods, may be not get the desired alternative for our problem.

2. We can modify our app to be able to work with Uri, then…

a. Use MediaStore, ContentResolver, and insert() to get a Uri for a particular type of media (e.g., an image)

MediaStore with ContentResolver

Apps that have a focus on media — audio, video, and images — can use the MediaStore.
Note, though, that you need READ_EXTERNAL_STORAGE to be able to see other apps’ content in the MediaStore.

This method is going to allow us to store our file wherever we want to but for accessing those files that are created by apps other than ours, this method provide us a URI(Uniform Resource Identifier) of the file instead of exact file location.
URI comes in a form that is not easily decodable, for example “content://media/external/images/media/307751”.

URI is provided by Content provider to the Content Resolver with a level of abstraction (Content Resolver doesn’t know from where Content Provider has brought that file from). Content Resolver then provides the URI to the user according to his request.

Below is the example of saving a text file in Documents folder of phone’s external storage using ContentResolver…

SAVING TEXT FILE…

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("Harish Gyanani $edt".toByteArray())                    it?.close()                }            }        }        read.setOnClickListener {            if (uri != null) {                resolver.openInputStream(uri).use {                    val data = ByteArray(50)                    it?.read(data)                    textView.text = String(data)                }            }        }    }}

For saving an image in DCIM folder, we can modify above code as…

SAVING IMAGE…

val resolver = this.contentResolverval contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, "CuteKitten001")put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/filename")}val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)resolver.openOutputStream(uri).use {// TODO something with the stream}

· Above example will only work with the android version Q/10 and above because only these versions support RELATIVE_PATH.

· ContentValues is a class that is used to store a set of values that the ContentResolver can process.

· We don’t need to call ContentProvider as it is automatically called by ContentResolver as per activity’s requirement.

· resolver.openOutputStream is used to perform write/modify/update function on the file located in external storage with the use of its uri (provided by ContentProvider).

· resolver.openInputStream is used to perform read function on the file.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store