Scoped Storage in Android 10+
Problem
Android 10 (API 29) introduced Scoped Storage, a major change to how apps access external storage. The traditional WRITE_EXTERNAL_STORAGE
permission behaves differently across Android versions:
- Android 9 and below: Full access to external storage when permission granted
- Android 10: Limited access unless using legacy flag
- Android 11+: Permission provides no access to most locations
Developers see lint warnings about WRITE_EXTERNAL_STORAGE
being ineffective on Android 10+, but removing it breaks functionality on older Android versions.
Solution
The optimal approach uses version-specific configurations and modern storage APIs.
AndroidManifest Configuration
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.app">
<!-- For Android 11+ full access (use cautiously) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- For Android 9 and below -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
<!-- Enable legacy storage for Android 10 -->
<application
android:requestLegacyExternalStorage="true"
...>
...
</application>
</manifest>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
WARNING
MANAGE_EXTERNAL_STORAGE
requires justification for Google Play approval and may be rejected if not for core functionality. Prefer MediaStore APIs when possible.
Runtime Permission Handling
fun checkStoragePermissions(activity: Activity): Boolean {
return when {
// Android 11+ requires special handling
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
if (!Environment.isExternalStorageManager()) {
val uri = Uri.parse("package:${activity.packageName}")
activity.startActivity(
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
)
false
} else {
true
}
}
// Android 10 - already handled by requestLegacyExternalStorage
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q -> true
// Android 6-9 need runtime permission
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
if (ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_CODE_STORAGE_PERMISSION
)
false
} else {
true
}
}
// Android 5.1 and below - permission granted at install time
else -> true
}
}
Version-Specific Considerations
Android Version | API Level | Required Approach |
---|---|---|
5.1 and below | ≤ 22 | No runtime permission needed |
6.0 - 9.0 | 23-28 | Request WRITE_EXTERNAL_STORAGE at runtime |
10 | 29 | Use requestLegacyExternalStorage="true" |
11+ | 30+ | Use MANAGE_EXTERNAL_STORAGE or MediaStore APIs |
Best Practices
Use app-specific directories when possible:
kotlinval appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
Prefer MediaStore API for shared media files:
kotlinval values = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "image.jpg") put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/MyApp") } val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
Avoid
MANAGE_EXTERNAL_STORAGE
unless absolutely necessary - Google Play has strict policy requirements for this permission.
Performance Consideration
The File API in Android 11+ is 2-3x slower as it's now a wrapper around MediaStore. For better performance, use MediaStore APIs directly.
Migration Path
For existing apps:
- Add
android:requestLegacyExternalStorage="true"
for Android 10 compatibility - Set
android:maxSdkVersion="28"
onWRITE_EXTERNAL_STORAGE
permission - Gradually migrate to MediaStore APIs for better long-term compatibility
- Only use
MANAGE_EXTERNAL_STORAGE
as a last resort with proper justification
This approach ensures backward compatibility while preparing your app for future Android versions and Scoped Storage requirements.