From Android 10 onward, Google changed the way that storage is handled. This became mandatory for all apps targeting Android 11 and above. The changes were implemented with privacy in mind, preventing users from being forced to grant access to every file on their device. This scoped storage tutorial will tell you what you need to know.
Scoped storage tutorial: The cliff-notes version
With scoped storage, users will need to grant permission any time an app attempts to access a file it didn’t create. Developers are also encouraged to place files in specific folders, thereby reducing the amount of clutter and disorganization. Any files outside of those folders will be deleted once an app is removed.
Scoped storage is granted by default and is based on the type of file being stored (these are organized as “collections”). Apps are only given access to the types of storage they actually use.
In practice, this means that devs no longer need to use the WRITE_EXTERNAL_STORAGE permission, as this has no effect on an app’s access to storage. Certain things get easier, others get a lot more complicated. Read the rest of this scoped storage tutorial to find out what you need to know.
Accessing app-specific storage
Apps need no special permissions to access scoped storage and there are no changes in the way that you save to internal storage. However, there are a few things to consider when using app-specific storage, which we will cover in this scoped storage tutorial.
There are two app-specific locations in external storage which should primarily be used when internal storage is insufficient. These are designated for the app’s persistent files and cached files, respectively.
To access these locations, the app must first verify that the storage is available (availability is guaranteed for internal storage). Query the volume’s state using Environment.getExternalStorageStage(). If MEDIA_MOUNTED is returned, you can read and write files to external storage.
Next, you will need to choose a physical storage location. This may mean choosing between internal memory vs an SD card, for example. Call ContextCompat.getExternalFilesDir(). The first element in the returned array is considered to be the “primary” external storage option, and this should be used in the majority of cases.
To access app-specific files from external storage, call getExternalFilesDir().
There are a number of ready-defined directories intended to aid with logical storage of files. These include:
Use these or pass “null” if you want to access the root domain for the app-specific directory.
To add app-specific files to the cache in external storage, use externalCacheDir. To remove them, use delete().
Accessing other files
Finally, to bring this scoped storage tutorial to a close, we must consider how we access and write files that are shared across the system.
Apps must choose between “app-specific storage” or “shared storage.” The latter is used when you want to share files with other apps, such as images or documents. Developers that wish to access files outside their app-specific directory should use the MANGE_EXTERNAL_STORAGE permission. You can also use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to send users the settings page where they can enable access for your app.
MANAGE_EXTERNAL_STORAGE grants access to the contents of the MediaStore.Files table, the root directory of the USB OTG drive and SD card, and all internal directories (except sdcard/Android and its subdirectories). This restriction includes the app-specific directories of other apps, as these are located in sdcard/Android/data.
We can no longer use File APIs to directly access files. Instead, we must use the Storage Access Framework for selecting files and folders, and URI’s for media files. To access a photo, for example, we must now use URIs rather than MediaStore.Images.Media.DATA. This was already the recommended practice, but is now mandatory.
For accessing simple files with the Storage Access Framework, there are three main actions:
These are fairly self-explanatory. For displaying images, we would first get the ID of the image with MediaStore.Images.Media._ID and then build the Uri with ContentUris.withAppendedID. This is a significantly lengthier process than it used to be and certainly increases the learning-curve for what could be a relatively simple process.
That wraps up this scoped storage tutorial! What do you think of scoped storage? Is it a necessary security update, or a frustrating hurdle for developers?
For more developer news, features, and tutorials from Android Authority, don’t miss signing up for the monthly newsletter below!
Join our developer crew to get a monthly developers newsletter curated by in-house experts!