SimpleStorage
πΎ Simplify Android Storage Access Framework for file management across API levels.
Install / Use
/learn @anggrayudi/SimpleStorageREADME
SimpleStorage
Table of Contents
- Overview
- Terminology
- Check Accessible Paths
- Read Files
- Manage Files
- Request Storage Access, Pick Folder & Files, Request Create File, etc.
- Activity Result Contracts
- Move & Copy: Files & Folders
- Search: Files & Folders
- Compress & Unzip: Files & Folders
- FAQ
- Other SimpleStorage Usage Examples
- License
Overview
The more higher API level, the more Google restricted file access on Android storage.
Although Storage Access Framework (SAF) is designed to secure user's storage from malicious apps,
but this makes us even more difficult in accessing files as a developer. Let's take an example where
java.io.File has been deprecated in Android 10.
Simple Storage ease you in accessing and managing files across API levels. If you want to know more about the background of this library, please read this article: Easy Storage Access Framework in Android with SimpleStorage
Adding Simple Storage into your project is pretty simple:
implementation "com.anggrayudi:storage:X.Y.Z"
// For Jetpack Compose
implementation "com.anggrayudi:storage-compose:X.Y.Z"
Where X.Y.Z is the library version:
All versions can be found here:
To use SNAPSHOT version, you need to add this URL to the root Gradle:
allprojects {
repositories {
google()
mavenCentral()
// add this line
maven { url "https://central.sonatype.com/repository/maven-snapshots/" }
}
}
Java Compatibility
Simple Storage is built in Kotlin. Follow this documentation to use it in your Java project.
Note that some long-running functions like copy, move, search, compress, and unzip are now only available in Kotlin. They are powered by Kotlin Coroutines & Flow, which are easy to use. You can still use these Java features in your project, but you will need v1.5.6 which is the latest version that supports Java.
Jetpack Compose
SimpleStorageHelper is a traditional class that helps you to request storage access, pick folders/files, and create files.
This class has interactive dialogs, so you don't have to handle storage access & permissions manually.
In Jetpack Compose, you can achieve the same thing with SimpleStorageCompose.kt.
This class contains composable functions:
rememberLauncherForStoragePermission()rememberLauncherForStorageAccess()rememberLauncherForFolderPicker()rememberLauncherForFilePicker()
If you think these composable functions has too many UI manipulations and don't suit your needs, then
you can copy the logic from SimpleStorageCompose.kt
and create your own composable functions. Because you might need custom dialogs, custom strings, etc.
For file creation, you can use rememberLauncherForActivityResult(FileCreationContract(context)).
Check all available contracts in the SimpleStorageResultContracts.kt
Terminology

Other Terminology
- Storage Permission β related to runtime permissions
- Storage Access β related to URI permissions
Check Accessible Paths
To check whether you have access to particular paths, call DocumentFileCompat.getAccessibleAbsolutePaths(). The results will look like this in breakpoint:

All paths in those locations are accessible via functions DocumentFileCompat.from*(), otherwise your action will be denied by the system if you want to
access paths other than those, then functions DocumentFileCompat.from*() (next section) will return null as well. On API 28-, you can obtain it by requesting
the runtime permission. For API 29+, it is obtained automatically by calling SimpleStorageHelper#requestStorageAccess() or
SimpleStorageHelper#openFolderPicker(). The granted paths are persisted by this library via ContentResolver#takePersistableUriPermission(),
so you don't need to remember them in preferences:
buttonSelectFolder.setOnClickListener {
storageHelper.openFolderPicker()
}
storageHelper.onFolderSelected = { requestCode, folder ->
// tell user the selected path
}
In the future, if you want to write files into the granted path, use DocumentFileCompat.fromFullPath():
val grantedPaths = DocumentFileCompat.getAccessibleAbsolutePaths(this)
val path = grantedPaths.values.firstOrNull()?.firstOrNull() ?: return
val folder = DocumentFileCompat.fromFullPath(this, path, requiresWriteAccess = true)
val file = folder?.makeFile(this, "notes", "text/plain")
Read Files
In Simple Storage, DocumentFile is used to access files when your app has been granted full storage access,
included URI permissions for read and write. Whereas MediaFile is used to access media files from MediaStore
without URI permissions to the storage.
You can read file with helper functions in DocumentFileCompat and MediaStoreCompat:
DocumentFileCompat
DocumentFileCompat.fromFullPath()DocumentFileCompat.fromSimplePath()DocumentFileCompat.fromFile()DocumentFileCompat.fromPublicFolder()
Example
val fileFromExternalStorage = DocumentFileCompat.fromSimplePath(context, basePath = "Download/MyMovie.mp4")
val fileFromSdCard = DocumentFileCompat.fromSimplePath(context, storageId = "9016-4EF8", basePath = "Download/MyMovie.mp4")
MediaStoreCompat
MediaStoreCompat.fromMediaId()MediaStoreCompat.fromFileName()MediaStoreCompat.fromRelativePath()MediaStoreCompat.fromFileNameContains()MediaStoreCompat.fromMimeType()MediaStoreCompat.fromMediaType()
Example
val myVideo = MediaStoreCompat.fromFileName(context, MediaType.DOWNLOADS, "MyMovie.mp4")
val imageList = MediaStoreCompat.fromMediaType(context, MediaType.IMAGE)
Manage Files
DocumentFile
Since java.io.File has been deprecated in Android 10, thus you have to use DocumentFile for file management.
Simple Storage adds Kotlin extension functions to DocumentFile, so you can manage files like this:
DocumentFile.getStorageId()DocumentFile.getStorageType()DocumentFile.getBasePath()DocumentFile.copyFileTo()List<DocumentFile>.moveTo()DocumentFile.search()DocumentFile.deleteRecursively()DocumentFile.getProperties()DocumentFile.openOutputStream(), and many moreβ¦
MediaFile
For media files, you can have similar capabilities to DocumentFile, i.e.:
MediaFile.absolutePathMediaFile.isPendingMediaFile.delete()MediaFile.renameTo()MediaFile.copyFileTo()MediaFile.moveFileTo()MediaFile.openInputStream()MediaFile.openOutputStream(), etc.
Request Storage Access, Pick Folder & Files, Request Create File, etc.
Although user has granted read and write permissions during runtime, your app may still does not have full access to the storage,
thus you cannot search, move and copy files. You can check whether you have the storage access via SimpleStorage.hasStorageAccess() or
DocumentFileCompat.getAccessibleAbsolutePaths().
To enable full storage access, you need to open SAF and let user grant URI permissions for read and write access.
This library provides you an helper class named SimpleStorageHelper to ease the request process:
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Only setup required callbacks, based on your need:
storageHelper.onStorageAccessGranted = { requestCode, root ->
// do stuff
}
storageHelper.onFolderSelected = { requestCode, folder ->
// do stuff
}
storageHelper.onFileSelected = { requestCode, files ->
// do stuff
}
storageHelper.onFileCr
