The CommonsBlog


Final "Elements of Android Q" Released

Subscribers now have access to the final version of Elements of Android Q, in PDF, EPUB, and MOBI/Kindle formats. Just log into your Warescription page to download it, or set up an account and subscribe!

Since the shipping version of Android 10 did not change much from the last few Q beta releases, there were few changes in this update. Mostly, it involved bug fixes.

This is the final update to this particular book, given that Android 10 has now shipped. If all goes well, I will have “Elements of Android R” available after Google releases the first beta of the 2020 flagship Android version.

Nov 11, 2019


Scoped Storage Stories: Trees

Android 10 is greatly restricting access to external storage via filesystem APIs. Instead, we need to use other APIs to work with content. This is the fourth post in a series where we will explore how to work with those alternatives, starting with the Storage Access Framework (SAF).


Working with individual pieces of content via ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT is not that difficult and is not that different than working with files.

However, suppose you need to work with several related pieces of content. For example, you want to offer a manual backup mechanism, and rather than backing up several files to a single ZIP, you want to offer a backup to a user-chosen directory.

In principle, you could still use ACTION_CREATE_DOCUMENT for this, asking the user for the location of each piece of content that you wish to create as part of the backup. This is annoying for the user, as they have to go through the ACTION_CREATE_DOCUMENT UI N times for your N pieces of content. And if the user screws something up — such as choosing different directories instead of the same directory for all of the content — your app may run into problems in consuming that content later on.

What would be better is if the user could create a directory for you, then grant you access to that entire directory. You could then put your content into that directory as you see fit.

Unfortunately, that is not completely supported. What is supported is for the user to choose some “directory”, via ACTION_OPEN_DOCUMENT_TREE, on Android 5.1+. Your app can then create its own “sub-directory” in the user-chosen location, then put your content there.

Here, I have “directory” and “sub-directory” in quotes, because technically that is not what you are working with. You are working with document trees, reflecting the name ACTION_OPEN_DOCUMENT_TREE. Whether or not a given document tree reflects some directory on some filesystem on some machine is up to the implementers of the user-selected document provider. In practice, it is likely to be a filesystem directory on the device, as few cloud storage providers seem to support ACTION_OPEN_DOCUMENT_TREE.

At the outset, you use ACTION_OPEN_DOCUMENT_TREE similarly to how you use ACTION_OPEN_DOCUMENT. You create an Intent with ACTION_OPEN_DOCUMENT_TREE as the action, then pass that Intent to startActivityForResult():

startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQUEST_TREE)

Then, in onActivityResult(), you can get a Uri that represents the tree:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  super.onActivityResult(requestCode, resultCode, data)

  if (resultCode == Activity.RESULT_OK) {
    data?.data?.let { useTheTree(it) }
  }
}

private fun useTheTree(root: Uri) {
  TODO("something useful, but on a background thread please")
}

From there, what you do will vary based on scenario.

Suppose you want to save a backup of several files, as suggested earlier. You could:

  • Wrap that Uri in a DocumentFile, via DocumentFile.fromTreeUri()

  • Call createDirectory() on that DocumentFile to create some sort of sub-tree and give you a DocumentFile pointing to it

  • Call createFile() on the sub-tree DocumentFile for each file that you want to back up, to get a DocumentFile representing the backup of that file

  • Call getUri() on the backup DocumentFile and use that with ContentResolver and openOutputStream() to get an OutputStream that you can use to create the backup itself

Suppose instead that you want to restore from the backup. If your instructions are for the user to choose the sub-tree that you created above, you could then do this with the Uri from ACTION_OPEN_DOCUMENT_TREE:

  • Wrap that Uri in a DocumentFile, via DocumentFile.fromTreeUri()

  • Call listFiles() to get an array of DocumentFile objects, representing the contents of that tree

  • Examine those to see if they look like your backed-up content, showing some error to the user if it looks like they chose the wrong place

  • Use getUri() for each of those DocumentFile objects and use that with ContentResolver and openInputStream() to get an InputStream that you can use to restore the content from the backup

There are many other patterns that you could follow — these are just for illustration purposes. The key is that you use DocumentFile much like you would use File for creating or iterating over the contents of a directory. While the details are different, the general approach is the same as with classic Java file I/O.


Previously, I covered:

In my next post in the series, I will point out the limitations of DocumentFile for batch operations, and will point out alternative approaches that will improve performance.

Nov 09, 2019


"Exploring Android" Version 0.9 Released

Subscribers now have access to an update to Exploring Android, known as Version 0.9, in PDF, EPUB, and MOBI/Kindle formats, in addition to the online reader. Just log into your Warescription page and download away, or set up an account and subscribe!


After an entirely-too-long delay (sorry!), I am finally getting back into updating my various books. First up is Exploring Android. Mostly, this update contains bug fixes, along with changes to support Android Studio 3.5.1. It also adopts Flow for getting query results from Room, which in turn affects how the ToDoRepository gets used and tested.

If you are wondering “what happened to 0.8?”, 0.9 is a version number that I use for a “release candidate”. I plan to publish 1.0 in early January, with no changes other than bug fixes and perhaps some cross-references with the other books. That might be followed fairly quickly by Version 1.1 for Android Studio 3.6, depending on Google’s IDE release schedule. From there, I will update the book a few times per year, based on Android Studio releases (and possibly Android releases, if some change breaks backwards compatibility).

Next, now that my Pixel 4 has arrived, I will wrap up the final edition of Elements of Android Q.

Nov 04, 2019


Scoped Storage Stories: DocumentFile

Android 10 is greatly restricting access to external storage via filesystem APIs. Instead, we need to use other APIs to work with content. This is the third post in a series where we will explore how to work with those alternatives, starting with the Storage Access Framework (SAF).


Whether you use ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT, you wind up with a Uri that points to the “document”. Your primary use of that Uri is with ContentResolver and its openInputStream() and openOutputStream() methods, so you can read and/or write the document. This is not that different from having a File and using the FileInputStream and FileOutputStream constructors to be able to read and/or write the document.

However, File has a lot of other useful methods, such as:

  • getName() to get the filename

  • exists() to confirm that an actual file exists at the path wrapped by the File

  • delete() to delete the file

  • and so on

The way to get similar functionality for your document Uri is via DocumentFile. DocumentFile can wrap a File or a document Uri and give you an API that resembles a subset of File functionality, including the three methods that I listed above.

If you have a document Uri, you can wrap it in a DocumentFile via DocumentFile.fromSingleUri(). You can then call getName(), exists(), delete(), and other methods, such as:

  • getType() to get the MIME type associated with the content

  • length() to get the length of the content (i.e., how many bytes you could read in from the InputStream)

  • renameTo() to rename the content

  • and so on

However, there are some limitations:

  • The “name” for a piece of content is not necessarily a filename with an extension. It is a “display name”, and it should be something that the user might recognize. However, if you are expecting SomethingLike-This.png, you may be disappointed. The display name might be a classic filename, but it does not have to be.

  • canWrite() is somewhat broken, in that it may return true for a Uri that you cannot write to

Note that you can also get a DocumentFile for a File via DocumentFile.fromFile(). This allows you to treat document Uri values and files the same, so that portions of your app can be written independently of where the data comes from.


Previously, I covered:

In my next post in this series… while I am not the Lorax, I will speak for the (document) trees.

Nov 02, 2019


Scoped Storage Stories: Durable Access

Android 10 is greatly restricting access to external storage via filesystem APIs. Instead, we need to use other APIs to work with content. This is the second post in a series where we will explore how to work with those alternatives, starting with the Storage Access Framework (SAF).


So, you used ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT to get a Uri that you can use to read and/or write some content. Great!

You got that Uri in the scope of some Activity. Perhaps you called startActivityForResult() directly on the Activity. Or, perhaps you called startActivityForResult() on some Fragment which, in turn, is managed by some Activity.

The good news is that your Activity should be able to work with that Uri, even across configuration changes.

The bad news is that your rights to that Uri content end there, by default.

Using the Uri in Other Components

You might try using that Uri from other components in your app. Perhaps you stick that Uri in some shared repository and have another Activity get that Uri and try using it. Or, perhaps you try passing the Uri to some Service, or try using it via WorkManager.

You will find out fairly quickly that your Uri rights vanish. You have access to the content identified by the Uri from the Activity that receives the Uri, and that’s it.

You can pass along those rights to other components of your app, or even components of other apps. To do that, though, you need to:

  • Pass the Uri in the “data” facet of an Intent

  • Add Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION to that Intent

  • Use that Intent to start the other component (e.g., startActivity(), startService())

val intent = Intent(this, TheOtherActivity::class.java)
  .setData(theUri)
  .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

startActivity(intent)

The Intent flags indicate that you want to pass along the read or write rights that your component has to the component that you are starting.

Note that this works for any content Uri, not just those from the Storage Access Framework. ACTION_GET_CONTENT, for example, works the same as does ACTION_OPEN_DOCUMENT in terms of these component-level access rights.

Getting Long-Term Access

Using the Intent flags has clear limitations:

  • Not everything offers you a place to put those flags (e.g., WorkManager)

  • Eventually, your process gets terminated, at which point all of your rights lapse

For Storage Access Framework Uri values, you can request long-term access to the content via takePersistableUriPermission() on a ContentResolver. You just pass in the Uri along with Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION. This will give you the ability to work with that Uri not only between components, but between process invocations. You can save the String representation of the Uri somewhere (e.g., SharedPreferences) and use it again in the future.

However, bear in mind that there is no guarantee that the underlying content itself is durable. The user might use some other app to move or delete that content, at which point your persisted Uri becomes pointless. You will need to be able to deal with the FileNotFoundException and related forms of IOException that you will get when this sort of thing occurs.

In addition to takePersistableUriPermission(), there is:

  • releasePersistableUriPermission(), to say that you no longer need durable access; and

  • getPersistedUriPermissions(), to retrieve information about all outstanding persisted permissions that you took using takePersistableUriPermission() and have not released using releasePersistableUriPermission()

What Else Is There?

Previously, I covered the basics of using the Storage Access Framework.

Upcoming posts in this series will include:

Oct 27, 2019


Older Posts