Android External Storage — Complete Guide
In this tutorial, you'll learn about Android External Storage. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your app can't access external storage on Android 11+, or your files created with getExternalStoragePublicDirectory() aren't visible in the file manager.
Wrong Approach ❌
// Using deprecated Environment.getExternalStoragePublicDirectory()
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(dir, "myapp_report.pdf")
// Requires WRITE_EXTERNAL_STORAGE which doesn't work on API 30+
Output: AccessDeniedException on Android 11+. Files not visible.
Right Approach ✅
class ExternalFileManager(private val context: Context) {
// App-specific external storage — no permissions needed (API 19+)
fun saveToAppExternal(filename: String, content: String) {
val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
if (dir != null) {
val file = File(dir, filename)
file.writeText(content)
}
}
// App-specific cache on external storage
fun saveToAppExternalCache(filename: String, data: ByteArray) {
val cacheDir = context.externalCacheDir
if (cacheDir != null) {
File(cacheDir, filename).writeBytes(data)
}
}
// For sharing files via FileProvider
fun getShareableUri(file: File): Uri {
return FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
}
// Media store for public files (API 29+)
fun saveToPublicDownloads(filename: String, content: ByteArray) {
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, filename)
put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
put(MediaStore.Downloads.IS_PENDING, 1)
}
val uri = context.contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
contentValues
)
uri?.let {
context.contentResolver.openOutputStream(it)?.use { output ->
output.write(content)
}
contentValues.clear()
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
context.contentResolver.update(it, contentValues, null, null)
}
}
// Check if external storage is available
fun isExternalStorageAvailable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
}
// FileProvider XML (res/xml/file_paths.xml)
// <paths>
// <external-files-path name="documents" path="Documents/" />
// </paths>
Output: Files saved correctly with proper API for each Android version.
Prevention
- Use
getExternalFilesDir()for app-specific files (no permissions needed). - Use
MediaStorefor public files on API 29+. - Use
FileProviderfor sharing files with other apps. - Don't request
WRITE_EXTERNAL_STORAGEon API 30+ — it's mostly ineffective.
Common Mistakes with storage external
- Using
returnto exit a function early instead of wrapping a pure value in the monad - Mixing let bindings with <- bindings in do notation, producing type errors
- Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro