Storage Access Framework (SAF) in Jetpack Compose
Jetpack compose is the new defacto for new android/kmm projects for 2023, for many of these projects the filesystem is a key part of the app, yet all these years it has been tough to get it right. but hopefully not anymore.
With the introduction of scoped storage on android 10 (Api Level 29), Google closed access to device external files (i.e. files that are outside your app-specific/cache directories), and in return presented these three alternative methods, more about them in the official documentation, we simply have
- MediaStore API
- Storage Access Framework (SAF)
- Manage Files Permission
One thing great about SAF
compared to the other APIs is that no permissions required in AndroidManifest.xml
for it to work, which means less hassle if you are publishing to Google Play. It works with Android Level 19 (Kitkat 4.4) 'til latest as of writing of this article. In fact the API was implemented alongside scoped storage to move apps from having full access to filesystem, of which google says developers abused this access for years for untended use and raised many security risks. All these points mean this API is built to stay. So how does it work?
Here is a simple example on opening a directory, and persisting the given Uri
If you are familiar with the old way of using ACTION_OPEN_DOCUMENT_TREE
intent, you know we are missing something. That is right, we are missing flags, we surely persist Uri with Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
, but if we don’t specify these flags to the rememberLauncherForActivityResult()
we cannot read/write to that given Uri, so how do we do it? Simply you have to subclass the ActivityResultContracts.OpenDocumentTree
class, and override the createIntent()
method to specify your prefered set of flags, here is the subclass that I work with called PermissibleOpenDocumentTreeContract
You can see that our new class is flexible in terms of permissions, if you need write permissions, pass true
to the constructor, otherwise, all is good, you can read more about FLAG_GRANT_PREFIX_URI_PERMISSION
and FLAG_GRANT_PERSISTABLE_URI_PERMISSION
here if you want. Now we update our SimpleScreen
, change the contract
parameter of rememberLauncherForActivityResult()
. Here is how it should look like
Note: the OpenDocumentTree
Activity result contract is supported from Android Level 21 (Lolipop 5.0), as is the example.
The code for this working app can be found here.
Happy coding.