Skip to content

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

xml
<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>
xml
<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

kotlin
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 VersionAPI LevelRequired Approach
5.1 and below≤ 22No runtime permission needed
6.0 - 9.023-28Request WRITE_EXTERNAL_STORAGE at runtime
1029Use requestLegacyExternalStorage="true"
11+30+Use MANAGE_EXTERNAL_STORAGE or MediaStore APIs

Best Practices

  1. Use app-specific directories when possible:

    kotlin
    val appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
  2. Prefer MediaStore API for shared media files:

    kotlin
    val 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)
  3. 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:

  1. Add android:requestLegacyExternalStorage="true" for Android 10 compatibility
  2. Set android:maxSdkVersion="28" on WRITE_EXTERNAL_STORAGE permission
  3. Gradually migrate to MediaStore APIs for better long-term compatibility
  4. 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.